Gary
Hsieh
2026 · taipeiloading
回到文章列表
軟體工程AI 趨勢2026-05-09

為什麼會有 Hook 存在?從 anti-rationalization 切入如何不讓 agent 惡搞,否則 prompt 寫得再好,LLM 都可以耍小聰明

為什麼會有 Hook 存在?從 anti-rationalization 切入如何不讓 agent 惡搞,否則 prompt 寫得再好,LLM 都可以耍小聰明

為什麼會有 Hook 存在?從 anti-rationalization 切入如何不讓 agent 惡搞,否則 prompt 寫得再好,LLM 都可以耍小聰明

最近讀了胡嘉璽老師的《Vibe Coding CLI 頂級開發:Claude Code 前瞻菁英育成手冊》 — 第十章專門在講 Hooks — 看完之後我一直在想一件事:為什麼這個機制會被設計出來?

書裡的範例都很實用 — 自動格式化、檔案保護、桌面通知、敏感檔案攔截 — 這些都是傳統 “Hook” 的應用。但我更想問:

在 AI Coding 時代,為什麼非有它不可?為什麼不能用 prompt 解決就好?

順著這個問題往下挖,我才發現它指向了一個更根本的現象:我們用 LLM 寫 code 的時候,常常會在 CLAUDE.md 或 system prompt 裡寫一些紀律 — 「先寫測試再寫實作」、「不要直接 push 到 main」、「commit message 要遵守 conventional」。

寫完之後跑一陣子,會發現一件事 — agent 有時候遵守、有時候不甩你,而且單次執行項目越多、越複雜的時候,超·級·明·顯·。不遵守的時候它還會給你一套說法 — 「這個 case 太簡單,unity test 測過就好」、「這次只是 typo 修正不需要 PR」、「我先寫到 deferred queue,等你解決 blocking issue」。

這就是 rationalization —— model 在壓力下會自我合理化來繞開規則。這不是 bug,這是它本來就會做的事,因為 autoregressive model 天生傾向生成語境上最合理、最容易延續的 explanation,而人類文本中自我合理化的、解釋自己的語彙模式本來就大量存在(我們不也常常騙自己、騙別人,承諾可以做到什麼,做不到後,再找理由圓場)。

我們在 prompt 裡寫禁止藉口、強迫 commitment、列出「不准這樣想」的紅旗清單 — 這套對抗 rationalization 的 prompt pattern,就叫 anti-rationalization

Hook 的存在,本質上就是為了處理 rationalization 永遠擋不完的問題 — 當 prompt 層的 anti-rationalization 也擋不住 agent 耍小聰明的時候,我們需要別的辦法,停止他。

為什麼 prompt 解擋不住?

Naive 解法基本上都在 prompt 層 — 寫更兇的指令、列出禁止的藉口、強迫 agent 先 commitment 再行動。obra/superpowers 這個 repo 把這套做到極致,有一套經典的 anti-rationalization block,把 model 會用的藉口跟現實對照列出來 —

  • 「太簡單不需要測試」→ 簡單的 code 也會壞
  • 「我已經手動測過了」→ 手動測試擋不住 regression
  • 「事後寫測試就好」→ 你會把測試寫成讓 code 能過
  • 「這次情況不一樣,略過」→ 不一樣個鬼,要重寫了

這套東西有它的學術背景 — Wharton 的 Lennart Meincke 跟 Robert Cialdin 2025 年一篇《Call Me A Jerk》做了 N=28,000 的實驗,發現 LLM 對人類的 7 個 persuasion 原則幾乎一樣有反應 — 平均合規率從 33% 跳到 72%(這篇學溝通心理的人比我熟很多…)。其中 commitment 原則最強(白話講就是「說到做到」— 強迫 agent 先口頭承諾要做什麼,它後續為了保持自我一致性,會更不容易違反)— 在某些情境能把合規率從 18% 拉到 100%。

這篇 paper 可作為「LLM 行為會被 persuasion framing 顯著影響」的旁證,但它不直接證明「anti-rationalization block 必然有效」。它只是告訴我們,prompt 層的說服性策略是有作用的,但作用是統計性的、不是保證性的

Prompt 永遠是 non-deterministic(非確定性)的 — 你寫得再嚴格,model 還是有非零機率會違反(我有時滿懶惰的,就跟它耗…)。而且還有更深的問題 — 當你寫 anti-rationalization block,本質上你還在跟 model 說服它別合理化 — 你在用它的語言、它的習慣、它的說服機制 — 你只是把同一個遊戲玩得比較進階。

這是一場永遠打不完的軍備競賽 — model 越強,rationalization 越精緻,prompt 也得越精緻 — 但 model 的訓練資料永遠比你的 prompt 多得多(這邊有點進到科幻了…人類未來沒辦法改寫機器人行為的話,那不就是這個模式了嗎?🫣)。

Hook 怎麼跳出說服遊戲

Claude Code 的 Hook 機制很單純 — 在 model 想呼叫某個 tool 的時候,先跑一段 shell script 檢查:

  • exit 0 就讓它過
  • exit 2 就直接擋下來,把錯誤訊息回灌給 model

想強制 TDD,只需要寫一段 5、6 行的 bash script 掛在 PreToolUse 上,比對 model 想寫的檔案路徑:如果是 production code(例如 .py 但不是 test_*.py),就檢查同目錄下有沒有對應的 test 檔;沒有的話 exit 2,附上錯誤訊息「先寫測試再來」。

這個 pattern 其實已經有人做成完整 plugin 了 — nizos/tdd-guard 這個開源專案就是專門做 TDD enforcement,跨 Claude Code、Codex、Copilot 都能用 — 它能 block 沒有 failing test 的 implementation、block 過度實作、整合 lint rule 強制 refactor。

Hook 這個簡單概念,做到了 prompt 永遠做不到的事:違反規則情況下,LLM 物理上不會寫出非你預期的 code

Hook 實務上在哪攔截

要理解為什麼這件事可能,得先理解 hook 到底攔在哪裡?

我本來猜測 hook 攔在 token streaming 中間,但其實不是。LLM 的輸出是 streaming 沒錯,可是 Anthropic API 的 stream 是有清楚分段事件的 — message 開始發一個事件、每個 content block 開始發一個事件、token 一段段流出來、block 結束發一個事件、最後整個 message 結束時,會有一個明確的 stop_reason 告訴外部系統「我這輪做完了」。

stop_reason: "tool_use" 跑出來,stream 已經完整收完,tool 名稱跟參數都拿到了 — Claude Code 這時候才去看 hook config 的 matcher 有沒有對到,對到才跑對應的 script。整個 agent loop 其實是一個外部 while 迴圈,跑的不是模型而是 harness:每一輪「呼叫 model → 看 stop_reason → 如果是 tool_use 就跑 hook → 沒被擋的話才執行 tool → 結果接回去 → 下一輪」。

所以 hook 攔不在 token-level,攔在 tool boundary — 也就是兩次 inference 中間的空隙

換個方式說 — Hook 之於 Claude Code,就像 syscall 之於 OS — application 想動硬體,要先發 syscall 給 OS,OS 在邊界檢查權限、必要時拒絕、必要時改寫參數。Hook 就是 LLM 世界的 syscall handler — 而 model 對應的是 user-space process,它以為自己在直接動 filesystem,其實每次都被 OS 攔了(這邊看不懂可以去看作業系統恐龍本)。

Hook 還有一個被低估的能力 — v2.0.10 之後,PreToolUse hook 不只能擋,還能修改 tool input。在 Claude Code 當前的實作下,hook 可以在 tool boundary 改寫 input,而修改後的內容未必完整回流到 model-visible context — 也就是 model 寫相對路徑、hook 自動轉絕對路徑;model 不小心把 API key 寫進來、hook 自動換成佔位符;危險指令自動加 --dry-run — model 看到的還是它原本寫的東西,它甚至不一定知道自己被改過。

Prompt 是修辭的政治 — 用語言改變 model 的行為機率。 Hook 是物理的工程 — 用程式碼改變 model 行為的可能性集合。

Hook 隱藏價值 — Token Economics

這個比較少人提,我自己在思考模型本質還是吐非預期文字後,思考了一下⋯都吐了,還來得及嗎?

Hook 攔截的時候,那個 tool_use 的 token 已經生成了 — 那一輪的成本省不掉 — 而且還會多花一輪 inference 處理 deny reason。聽起來 hook 是更貴的,但長遠來說沒有 hook 的時候,bad branch 可以一路走下去 — model 寫了 production code、跑出 bug、再寫 fix、再 fix、再合理化、再寫 fix,每一步都消耗 token、每一步都污染 context,浪費是不會停止的(開發的時間也是)。

有 hook 之後,bad branch 的成本被控制 — 一次 generation + 一次 deny reason — 然後 model 被迫切到正確路徑。演算法來類比,Hook 是 alpha-beta pruning 在 LLM agent loop 上的對應物 — 用一次攔截的成本換整條錯誤路徑的擴散。Prompt 解的失敗成本是 unbounded(取決於 model 能 rationalize 多遠),hook 解的失敗成本是 bounded(一次 generation 的 token)。

把 scaffolding 從 non-deterministic 推向 deterministic — 這不只是品質工程,也是 token 經濟學工程,也是開發效率控管。寫 hook 看起來是在多花力氣,實際上是在用工程努力換取每次失敗的 token 上限。

Enforcement Stack 的三層

把這個對比拉開,其實所有 enforcement 機制可以排成一個 stack:

  • 第一層 — Harness(non-deterministic):CLAUDE.md、Skills、Plan mode、anti-rationalization block。這層全部是 prompt-level,developer 完全擁有。
  • 第二層 — Tools(deterministic):Hooks、Permission system、Tool input modification、Sub-agent tool restrictions。這層是程式碼層,developer 完全擁有。
  • 第三層 — Model(intrinsic):RLHF、Constitutional AI、process reward model。這層在模型權重裡,developer 完全動不了,這是 Anthropic 的領地。

這個 stack 有兩個 invariant:

  • Invariant 1 — Strength 跟 ownership 反向:越往下越強、越貴、越不靈活;越往上越弱、越便宜、越靈活 — 這意味著一件殘酷的事:developer 真正能完整擁有的最深一層,就是 hook,再往下,是 LLM 供應商在做什麼決定了你能做什麼。
  • Invariant 2 — 同一個紀律可以實作在不同層:例如「先寫測試再寫 code」這條規則 — 寫在 CLAUDE.md(第一層,弱、便宜、可以隨時改)、寫成 hook(第二層,強、要寫程式、改起來慢)、或期望 model 預設就遵守(第三層,我們動不了,得等 Anthropic)。

選擇實作在哪一層,就是工程判斷。這個 framing 把所有 enforcement 工具放進同一個座標系,包括還沒出現的工具:L1 是說服模型、L2 是限制模型、L3 是塑造模型 — 三種完全不同的工程姿態。

其他 Coding Agent 怎麼做

把這個 stack 攤開,順便看看其他 coding agent 在第二層提供什麼:

  • Cursor.cursor/rules/*.mdc、User / Project / Team Rules — 本質上更接近 prompt / context layer,即使有 team-level enforce 設定,也不是通用的 tool-boundary hook 機制。Knostic Labs 提到:「Cursor doesn't follow rules because it is fundamentally a prediction engine rather than a policy enforcer」。
  • GitHub Copilot.github/copilot-instructions.md 跟 custom instructions(個人、repo、organization 三層)— 主要還是 instruction layer,給 Copilot 額外 context 而不是攔截行動。
  • Codex CLIAGENTS.md 是 prompt layer,同時有 OS-enforced sandbox 跟 approval policy。值得一提的是,截至 2026,Codex 也加入了 hooks,包含 PreToolUse、PermissionRequest、PostToolUse、UserPromptSubmit、Stop。
  • Gemini CLIGEMINI.md、extensions、MCP、custom commands,加上 sandbox — 但目前不是以 lifecycle hook / PreToolUse gate 作為主要產品心智模型。

在 deterministic enforcement 這件事上,Claude Code 仍是主流 coding agent 中最早、也最成熟地把 hooks 當成 first-class workflow 的代表之一,但這條線正在被追上來。

這個差異的根源不只是工程選擇,是用戶族群選擇。Cursor 是 VS Code fork — 它預設用戶是 IDE 重度使用者,寫 bash、配置 JSON、debug shell script 不是這群人的舒適區。Claude Code 跟 Codex CLI 反過來,預設用戶是 terminal-first 開發者 — 這群人本來就習慣 git hook、systemd unit file、.claude/settings.json 這種 lifecycle 配置 pattern。

兩種 enforcement 哲學從根上就分岔 — IDE-first 假設用戶不會寫 bash、假設 model 會聽話;Terminal-first 假設用戶會寫 bash、假設 model 不會聽話。第一個假設決定了能寫多硬的 enforcement,第二個假設決定了你願意承擔多少 enforcement 工作。選 coding agent 這件事,從來不只是選哪個 model 強 — Anthropic 跟 OpenAI 的 model 在誰家上跑都一樣 — 是選我們願意把 enforcement 工作放在哪一層。

Hook 該用在哪、不該用在哪

知道 stack 之後,回到實務上,用 hook 不是越多越好,反而是知道「哪些紀律該住在這一層」比 Hook 的代價更重要:

  • 延遲 — 每個 PreToolUse hook 都加 latency,業界共識 sub-100ms 才合格,一個 5 秒的 hook 等於每次 tool call 多 5 秒。
  • 維護 — Hook 規則本身會 drift,一年沒看就忘了當初為什麼擋,而且會跟 codebase 結構耦合,refactor 之後常常壞。
  • 過度 enforcement — 太多 hook 會把 agent 卡死、退化成傳統 deterministic workflow,失去 agentic 本來的彈性(這個我覺得是要最小心的)。
  • 探索期、prototype 不用 — Spec / plan 還在動的時候,agent 需要自由度。
  • 個人偏好用 prompt,團隊紀律用 hook — 個人風格不遵守就算了,團隊紀律不能妥協。
  • 能傳統在 lint / CI 解的不要用 hook — 同樣紀律可以實作在不同層:agent runtime 層擋(hook)、commit 層擋(pre-commit)、CI 層擋(pipeline)— 越早攔越省 token,但不是所有紀律都需要在 agent runtime 層擋。

該不該用 hook 的提問就三個:這條紀律違反的成本有多高?容忍度有多低?發生頻率有多高?三個答案決定它應該住在 stack 的哪一層。

最大的 takeaway

研究完 Hook 後,做 harness engineering 本質是在問:「約束(enforcement / harness)」應該住在 stack 的哪一層?prompt 解便宜但會漏、hook 解貴但設計麻煩、model 解最強但碰不了 — 三層各有甜蜜點,也各有代價。

從說服模型到約束模型,這是兩種完全不同的工程姿態:

說服是修辭,約束是工程。 說服的天花板是 model 的訓練資料,約束的天花板是能寫多深的 boundary。

回到《Vibe Coding CLI 頂級開發》裡介紹的九種 hook,現在再看會有新的理解。PreToolUse、PreCompact 這兩個是 harness engineering 的核心武器 — 都發生在「壞事還沒發生」之前,個人開發者最值得花時間在這裡(PreToolUse 攔截 rationalization 落地的瞬間,PreCompact 在 context 被壓縮前留下重要狀態)。SessionStart、PostToolUse、Notification 這幾個比較適合團隊紀律跟 observability —— SessionStart 注入專案 context、PostToolUse 跑團隊統一的 lint、Notification 整合到 Slack,是把個人 hook 升級成團隊 enforcement 的橋樑。

哪層該說服、哪層該約束、哪層放手交給 LLM 供應商 — 這個判斷力,比知道任何單一工具更重要

這個 framing 不只適用於 anti-rationalization,任何「我希望 agent 怎樣怎樣」的問題都可以用這個 stack 想一遍 — code review、commit message、API design、test coverage… 每一條紀律都可以問「做在哪一層最划算」。

#AI#Agent#LLM#Claude Code#Claude