用 Claude Code 寫程式,有些事不能靠「建議」— 你需要系統層級的保證
CLAUDE.md 是「建議」,Hooks 是「法律」。當你需要 100% 執行率而不是 80%,就需要系統層級的保證。完整解析 Claude Code Hooks 機制、設定方式、實戰範例,讓你的 AI 開發工作流從「大部分時候有做」變成「每次都做」。

用 Claude Code 寫程式的人,遲早會撞到這面牆
很多人 Vibe Coding 用 Claude Code 做開發,初期體驗很好 — 丟需求,出程式碼。
但隨著專案變複雜,一個結構性問題會浮現:
你在 CLAUDE.md 裡寫了「每次 commit 前跑 test」、「改完程式要跑 Prettier」,但 AI 不會每次都做。
有時候記得,有時候忘記。Session 越長,遺忘率越高。
這不是 AI 的 bug,是架構設計的必然結果 — CLAUDE.md 本質上是一份「建議書」。AI 會盡量遵守,但它是概率性的,你的指令會在長 context 中被稀釋。
問題的本質:有些事情需要 100% 執行率,不是 80%。
格式化必須每次都跑。危險指令必須每次都攔截。Type check 必須每次都做。「大部分時候會做」跟「每次都做」之間的差距,就是 bug 和穩定之間的差距。
解法:CLAUDE.md 管判斷,Hooks 管紀律
Claude Code 有一個叫 Hooks 的機制,專門解決這個問題。
Hooks 讓你在 AI 工作流的特定時間點,綁定一個「必定執行」的動作:
- AI 改完檔案 → 自動跑 Prettier
- AI 要執行 bash 指令 → 自動檢查有沒有危險操作
- AI 完成任務 → 自動發桌面通知
關鍵字是「自動」和「必定」。不是 AI 決定要不要做,是系統層級保證會做。
如果 CLAUDE.md 是「建議」,Hooks 就是「法律」。
這個區分很重要:CLAUDE.md 管的是 AI 的「判斷」— 什麼技術選型、coding style。Hooks 管的是 AI 的「紀律」— 什麼事情必須做、什麼操作絕對不能做。兩者搭配才是完整的 AI 開發工作流。
怎麼設定:比想像中簡單
最快的方式:直接叫 Claude 幫你設
在 Claude Code 裡面直接用自然語言描述你要的 Hook,例如:
幫我加一個 Hook,每次編輯 .ts 檔案之後自動跑 Prettier
Claude 會自動幫你寫好 JSON 設定並存檔。
你也可以打 /hooks 查看目前已設定的 Hook(注意:/hooks 選單是唯讀的,只能查看,不能新增或修改)。
不需要改任何程式碼,不需要裝任何套件。
進階方式:直接寫 JSON
在 .claude/settings.json 加入設定:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}意思是:每當 AI 編輯或寫入檔案,自動對該檔案跑 Prettier。
核心觸發時機,覆蓋整個 AI 工作流
以下列出最常用的 7 個 Hook 事件。Claude Code 實際支援超過 15 個事件(包括 SubagentStop、SessionEnd、PreCompact、PermissionRequest 等),完整列表請參考官方文件。
| 觸發時機 | 什麼時候觸發 | 實際用途 |
|---|---|---|
| SessionStart | 開啟新 session | 載入額外 context、檢查環境 |
| UserPromptSubmit | 送出 prompt 後 | 記錄日誌、注入 context |
| PreToolUse | AI 執行操作前 | 攔截危險指令 |
| PostToolUse | 工具執行完成後 | 自動格式化、type check |
| Notification | AI 發通知時 | 桌面通知提醒 |
| Stop | AI 完成回應時 | 跑測試、驗證結果 |
| Setup | 專案初始化時 | 安裝依賴、build |
四個實戰 Hook 設定
1. 自動格式化
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.file_path' | { read fp; if echo \"$fp\" | grep -qE '\\.(ts|tsx|js|jsx)$'; then npx prettier --write \"$fp\"; fi; }"
}]
}只對 TypeScript / JavaScript 檔案執行。AI 改完檔案的瞬間,Prettier 就跑了。再也不需要手動跑。
2. 危險指令攔截
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.command' | { read cmd; if echo \"$cmd\" | grep -qE 'rm\\s+-rf\\s+/'; then echo 'Blocked: dangerous rm command' >&2; exit 2; fi; }"
}]
}exit 2 是關鍵 — 它是「絕對否決權」。不管 AI 想做什麼,exit code 2 都能完全阻止,並把錯誤訊息傳回給 AI。
3. 桌面通知
在 .claude/settings.json 加入 Notification event 的 Hook 即可(見下方完整設定範例)。AI 跑耗時任務時可以切去做其他事,完成後收到系統通知。
4. TypeScript Type Check
{
"matcher": "Edit|MultiEdit",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.file_path' | { read fp; if echo \"$fp\" | grep -qE '\\.(ts|tsx)$'; then npx tsc --noEmit --skipLibCheck 2>&1 | head -20 || true; fi; }"
}]
}進階:Async Hooks — 不阻塞的背景執行

上面的 Hook 都是「同步」的 — AI 會等它跑完才繼續。對格式化和危險指令攔截來說,這是必要的。
但某些 Hook 根本不需要阻塞 AI:記錄日誌、發通知、上傳指標。這些「射後不理」的任務,加一行 "async": true 就能讓它們在背景執行:
{
"type": "command",
"command": "./log-bash-usage.sh",
"async": true,
"timeout": 30
}AI 不會等這個 Hook 跑完,直接繼續工作。如果你的專案 Hook 越來越多,每次互動都要等好幾秒,async 可以大幅提速。
什麼應該用 async?
- ✅ 日誌記錄、Slack 通知、metrics 上傳 →
async: true - ❌ 格式化、危險指令攔截、Type check → 保持同步
判斷標準很簡單:如果這個 Hook 需要影響 AI 接下來的行為,就用同步。如果只是觀察和報告,就用 async。
Exit Code 控制流程
| Exit Code | 效果 |
|---|---|
| 0 | 正常,繼續執行 |
| 其他非零 | 有錯誤,但不阻止 AI(stderr 顯示於 verbose 模式) |
| 2 | 完全阻止操作,把 stderr 傳回給 AI |
exit 2 是整個 Hooks 系統最強大的部分。它讓你可以在 AI 執行任何操作之前先「審查」,不符合條件就直接擋掉。
設定檔放哪裡
| 位置 | 檔案路徑 | 適合什麼 |
|---|---|---|
| 全域 | ~/.claude/settings.json | 所有專案都要用的 Hook |
| 專案 | .claude/settings.json | 團隊共用,加入版控 |
| 個人 | .claude/settings.local.json | 只有自己用,不加入版控 |
團隊協作的做法:共用的 Hook(格式化、lint)放專案設定並加入 git,個人化的(通知、日誌)放 local 設定。
可以直接複製的完整設定
以下組合適合大部分 TypeScript / JavaScript 專案,複製到 .claude/settings.json 即可(macOS 環境):
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.file_path' | { read fp; if echo \"$fp\" | grep -qE '\\.(ts|tsx|js|jsx)$'; then npx prettier --write \"$fp\"; fi; }"
}]
},
{
"matcher": "Edit|MultiEdit",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.file_path' | { read fp; if echo \"$fp\" | grep -qE '\\.(ts|tsx)$'; then npx tsc --noEmit --skipLibCheck 2>&1 | head -20 || true; fi; }"
}]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "jq -r '.tool_input.command' | { read cmd; if echo \"$cmd\" | grep -qE 'rm\\s+-rf\\s+/'; then echo 'Blocked: dangerous rm command' >&2; exit 2; fi; }"
}]
}
],
"Notification": [
{
"hooks": [{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code 需要你的注意\" with title \"Claude Code\"'"
}]
}
]
}
}安全性注意事項
Hooks 用系統權限執行,沒有沙箱隔離:
- Hook 能做任何你能做的事
- Clone 別人的專案時,先檢查
.claude/settings.json裡有沒有異常的 Hook - 非共用的 Hook 放在
.claude/settings.local.json,不加入版控
核心 Insight
這個「建議 vs 規則」的框架不只適用於 Claude Code。
任何 AI 自動化工具,都需要思考同一個問題:哪些事情「建議 AI 做」就夠了,哪些事情必須用系統層級保證?
把 AI 當「聰明但不完全可靠的助手」來管理,而不是當「完美的執行者」來期待 — 這條線搞清楚,對 AI 工具的掌控力會完全不同。