AI 程式設計教程中文版
官方教程中文版擴充套件與自動化

使用 Hooks

Claude Code Hooks 在生命週期事件上執行確定性自動化。學會事件、matcher、if、exit code、JSON 輸出、command/prompt/agent/HTTP hooks 和安全邊界。

Hook 是把“希望 Claude 記得做”變成“事件發生時一定執行”。凡是必須發生、可指令碼化、可判定的動作,都不應該只寫在 prompt 裡。——翔宇

這一章用 16 分鐘換什麼:前面講了 Skills 和 Subagents。Skills 給 Claude 方法,Subagents 做隔離任務,Hooks 則在 Claude Code 生命週期上做確定性自動化。讀完你應該能配置通知、格式化、阻斷、審計、環境過載、permission 自動處理,並知道什麼時候該用 command、prompt、agent 或 HTTP hook。

1. Hook 解決什麼問題

Hook 是在 Claude Code 生命週期事件上自動執行的動作。

典型用途:

  • Claude 等待輸入時發通知。
  • Edit / Write 後自動格式化。
  • PreToolUse 阻止危險命令。
  • 禁止修改 .env.git/、lockfile 等受保護檔案。
  • compaction 後重新注入關鍵上下文。
  • 配置檔案變化時審計。
  • 目錄變化時過載 direnv、devbox、nix 環境。
  • PermissionRequest 出現時自動處理特定低風險請求。
  • Subagent 開始和結束時做 setup / cleanup。

Hook 和 prompt 的區別:

  • Prompt 是讓 Claude 記住並判斷。
  • Hook 是事件發生時直接執行。

第一性原理:需要推理和自由裁量,用 Skill 或 Subagent;需要每次固定執行,用 Hook;需要安全硬邊界,優先 permissions + Hook。

flowchart TD
    Rule["一條規則或動作"]
    MustRun["必須每次發生?"]
    NeedsLLM["需要模型判斷?"]
    NeedsTools["需要讀檔案或跑命令判斷?"]
    External["需要通知外部系統?"]

    Prompt["CLAUDE.md / Skill"]
    CommandHook["Command Hook"]
    PromptHook["Prompt Hook"]
    AgentHook["Agent Hook"]
    HttpHook["HTTP Hook"]

    Rule --> MustRun
    MustRun -->|否| Prompt
    MustRun -->|是| NeedsLLM
    NeedsLLM -->|否| External
    External -->|是| HttpHook
    External -->|否| CommandHook
    NeedsLLM -->|是| NeedsTools
    NeedsTools -->|否| PromptHook
    NeedsTools -->|是| AgentHook

    style CommandHook fill:#dcfce7,stroke:#22c55e,stroke-width:2px
    style PromptHook fill:#e0f2fe,stroke:#0284c7,stroke-width:2px
    style AgentHook fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
    style HttpHook fill:#fee2e2,stroke:#ef4444,stroke-width:2px

2. Hook 和 Skill 的邊界

Skill 適合:

  • 釋出 checklist。
  • 程式碼審查流程。
  • API 風格指南。
  • 除錯 playbook。
  • 需要 Claude 推理、取捨、適配上下文的工作方法。

Hook 適合:

  • 每次編輯後跑 formatter。
  • 每次 Bash 前檢查命令。
  • 每次會話結束寫日誌。
  • 每次 permission prompt 出現時處理某類低風險請求。
  • 每次配置檔案變化時審計。

錯誤用法:

  • “不要改 .env”只寫在 Skill 裡。
  • “每次儲存後格式化”只寫在 CLAUDE.md 裡。
  • “釋出前一定跑測試”只靠 Claude 自覺。

正確做法:

  • 知識和流程寫 Skill。
  • 確定動作寫 Hook。
  • 安全拒絕寫 permissions deny,必要時再加 Hook 給反饋。

Hook 不是更強提示詞:它是自動化執行點。不要把需要人類判斷的危險動作做成無確認 Hook。

3. Hook 配在哪裡

Hook 寫在 settings 裡:

  • User:~/.claude/settings.json
  • Project:.claude/settings.json
  • Local:.claude/settings.local.json
  • Managed:組織級 managed settings
  • Plugin:plugin 裡的 hook 配置
  • Subagent frontmatter:只在該 subagent 生命週期內生效

基礎結構:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

同一個 hooks object 裡可以有多個事件。不要新增一個 hooks key 把舊配置覆蓋掉。

檢視入口:

/hooks

官方說明 /hooks 是隻讀瀏覽器。新增、修改、刪除仍然要編輯 settings JSON,或讓 Claude 幫你改配置檔案。

Project Hook 適合團隊規則;User Hook 適合個人通知和日誌;Local Hook 適合本機路徑;Managed Hook 適合組織強制自動化。

4. 第一個 Hook:等待輸入時發通知

macOS 示例:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

驗證:

  1. 寫入 ~/.claude/settings.json
  2. 執行 /hooks,確認 Notification 下有配置。
  3. 觸發一個 permission prompt 或讓 Claude 完成任務等待輸入。

macOS 如果沒有通知,先在 Terminal 跑一次:

osascript -e 'display notification "test"'

然後去 System Settings 裡給 Script Editor 開通知許可權。

Notification 的空 matcher 表示全部通知型別。只想匹配許可權提示時,用 permission_prompt

5. 常見事件:先記這幾類

Hook 事件很多,新手先記這幾類。

  • SessionStart:會話開始或恢復。
  • UserPromptSubmit:使用者 prompt 提交後、Claude 處理前。
  • PreToolUse:工具執行前,可以阻斷。
  • PermissionRequest:許可權彈出視窗即將出現。
  • PermissionDenied:auto mode 分類器拒絕工具呼叫。
  • PostToolUse:工具成功執行後。
  • PostToolUseFailure:工具失敗後。
  • PostToolBatch:一批並行工具呼叫完成後。
  • Notification:Claude Code 傳送通知時。
  • SubagentStart / SubagentStop:subagent 開始和結束。
  • TaskCreated / TaskCompleted:任務建立和完成。
  • Stop:Claude 完成響應。
  • StopFailure:本輪因 API 錯誤結束。
  • ConfigChange:配置檔案變化。
  • CwdChanged:工作目錄變化。
  • FileChanged:被監聽檔案變化。
  • WorktreeCreate / WorktreeRemove:worktree 建立和移除。
  • PreCompact / PostCompact:compaction 前後。
  • InstructionsLoadedCLAUDE.md 或 rules 載入到上下文。
  • Elicitation / ElicitationResult:MCP elicitation 相關事件。

事件決定能力邊界:阻斷工具要用 PreToolUse;工具完成後格式化用 PostToolUse;許可權彈出視窗自動處理用 PermissionRequest;上下文補充用 SessionStartPostCompact

6. Hook 怎麼收到輸入

Command hook 會從 stdin 收到 JSON。

常見欄位包括:

  • hook_event_name
  • session_id
  • transcript_path
  • cwd
  • tool_name
  • tool_input
  • 事件專屬欄位

例如 PreToolUse / PostToolUse 裡,通常會有:

{
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "git status"
  }
}

指令碼里一般用 jq 解析:

input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')

先儲存輸入樣本:寫複雜 Hook 前,先把 stdin JSON append 到臨時日誌,確認欄位名和事件結構。

7. Exit code 語義

Command hook 透過 exit code 和 stdout / stderr 告訴 Claude Code 下一步怎麼做。

  • Exit 0:繼續。對 UserPromptSubmitUserPromptExpansionSessionStart 等事件,stdout 會加入 Claude 上下文。
  • Exit 2:嘗試阻斷。stderr 會反饋給 Claude 或使用者。對部分事件不能阻斷,只會顯示錯誤後繼續。
  • 其他 exit code:非阻斷錯誤。流程繼續,stderr 第一行顯示在 transcript,完整 stderr 寫 debug log。

例子:阻止 Bash 裡出現危險 SQL。

#!/usr/bin/env bash
set -euo pipefail

input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')

if echo "$command" | rg -i "drop table|delete from|truncate table" >/dev/null; then
  echo "Blocked: destructive SQL is not allowed" >&2
  exit 2
fi

exit 0

不要混用 exit 2 和 JSON allow/deny:官方說明 exit 2 時 JSON 會被忽略。要結構化控制,用 exit 0 + stdout JSON。

8. Structured JSON 輸出

Exit code 只能粗略 allow / block。需要更細控制時,exit 0 並向 stdout 輸出 JSON。

PreToolUse 可以返回:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Use rg instead of grep for repository search."
  }
}

效果:

  • 工具呼叫被取消。
  • permissionDecisionReason 會反饋給 Claude。
  • Claude 可以調整做法後繼續。

PermissionRequest 可以返回 allow:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow"
    }
  }
}

也可以更新當前 session 的許可權模式:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedPermissions": [
        { "type": "setMode", "mode": "acceptEdits", "destination": "session" }
      ]
    }
  }
}

自動 allow 要極窄PermissionRequest matcher 不要寫空或 .*,否則可能自動批准檔案寫入、shell 命令和外部工具。

9. matcher 怎麼工作

matcher 用來篩選某類事件。

不同事件的 matcher 匹配物件不同:

  • PreToolUse / PostToolUse:工具名,例如 BashEditWritemcp__github__.*
  • Notification:通知型別,例如 permission_promptidle_prompt
  • SubagentStart / SubagentStop:agent type,例如 ExplorePlan、custom agent name。
  • PreCompact / PostCompactmanualauto
  • ConfigChangeuser_settingsproject_settingslocal_settingspolicy_settingsskills
  • FileChanged:要監聽的 literal filenames,例如 .envrc|.env
  • SessionEnd:結束原因,例如 clear

一些事件不支援 matcher,會每次觸發:

  • UserPromptSubmit
  • PostToolBatch
  • Stop
  • TaskCreated
  • TaskCompleted
  • CwdChanged
  • WorktreeCreate
  • WorktreeRemove

先用 matcher 粗篩:例如 Edit|Write。再用 if 或指令碼內部邏輯做細篩。

10. if 欄位:按工具名和引數過濾

if 欄位要求 Claude Code v2.1.85 或更高。舊版本會忽略它,導致 hook 對所有 matcher 命中的呼叫執行。

if 使用和 permissions 相同的規則語法。

例子:只檢查 git 命令,而不是所有 Bash。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(git *)",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-git-policy.sh"
          }
        ]
      }
    ]
  }
}

if 只適用於工具類事件:

  • PreToolUse
  • PostToolUse
  • PostToolUseFailure
  • PermissionRequest
  • PermissionDenied

if 放到其他事件上,會阻止 hook 執行。

matcher 按事件物件過濾,if 按工具和引數過濾。需要按 Bash 子命令、Edit 路徑、MCP tool 細分時,用 if

11. PostToolUse:編輯後自動格式化

專案級格式化適合放 .claude/settings.json

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

這個 Hook 的執行邏輯:

  1. Claude 用 EditWrite 改檔案。
  2. Hook 從 JSON 裡取 .tool_input.file_path
  3. 把檔案路徑傳給 Prettier。

邊界:

  • 如果專案不用 Prettier,不要套用這個例子。
  • Python、Go、Rust 等專案應該替換成對應 formatter。
  • 格式化失敗不要靜默吞掉,要讓 Claude 看到錯誤。

格式化適合 PostToolUse:不要讓 Claude 自己記住“改完跑 formatter”。讓 Hook 負責。

12. PreToolUse:阻止受保護檔案

指令碼示例:.claude/hooks/protect-files.sh

#!/usr/bin/env bash
set -euo pipefail

input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

case "$file_path" in
  *.env|*.env.*|*/.git/*|*package-lock.json)
    echo "Blocked: protected file path $file_path" >&2
    exit 2
    ;;
esac

exit 0

註冊:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

指令碼要可執行:

chmod +x .claude/hooks/protect-files.sh

保護敏感檔案優先用 permissions deny。Hook 的價值是給更具體的反饋和處理複雜條件。

13. SessionStart / PostCompact:注入上下文

SessionStart 可以在會話開始、恢復、clear 或 compact 後注入上下文。

示例:compaction 後提醒關鍵規則。

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Reminder: use pnpm, run pnpm test before committing, and do not edit generated files.'"
          }
        ]
      }
    ]
  }
}

注意:

  • 每次都要知道的穩定規則,優先寫 CLAUDE.md
  • Hook 注入適合動態資訊,例如最近 commit、當前 sprint、環境狀態。
  • 不要注入大段內容,避免上下文汙染。

靜態規則進 CLAUDE.md,動態上下文用 Hook

14. ConfigChange:審計配置變化

ConfigChange 在配置檔案被外部程序或編輯器修改時觸發。

示例:記錄配置變化。

{
  "hooks": {
    "ConfigChange": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{timestamp: now | todate, source: .source, file: .file_path}' >> ~/claude-config-audit.log"
          }
        ]
      }
    ]
  }
}

matcher 可以過濾:

  • user_settings
  • project_settings
  • local_settings
  • policy_settings
  • skills

要阻止變化生效,可以 exit 2,或返回 structured JSON block。

審計日誌不要寫敏感值:只記錄檔案、來源、時間和動作,避免把 token、路徑隱私和賬號寫進日誌。

15. CwdChanged / FileChanged:過載環境

有些專案依賴 direnv、devbox、nix,根據目錄或檔案變化載入環境。

SessionStart + CwdChanged 示例:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ],
    "CwdChanged": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ]
  }
}

監聽特定檔案:

{
  "hooks": {
    "FileChanged": [
      {
        "matcher": ".envrc|.env",
        "hooks": [
          {
            "type": "command",
            "command": "direnv export bash > \"$CLAUDE_ENV_FILE\""
          }
        ]
      }
    ]
  }
}

FileChanged 的 matcher 是 literal filenames 列表,不是正則。

載入 .env 要小心:不要把敏感值列印到 stdout。環境注入寫 CLAUDE_ENV_FILE,不要回顯到對話。

16. PermissionRequest:自動批准要極窄

自動批准某個低風險許可權請求可以用 PermissionRequest

示例:只自動批准 ExitPlanMode

{
  "hooks": {
    "PermissionRequest": [
      {
        "matcher": "ExitPlanMode",
        "hooks": [
          {
            "type": "command",
            "command": "echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PermissionRequest\",\"decision\":{\"behavior\":\"allow\"}}}'"
          }
        ]
      }
    ]
  }
}

不要這樣做:

  • 空 matcher 自動批准所有請求。
  • .* 自動批准所有請求。
  • BashEdit、MCP 寫操作做無條件 allow。

PermissionRequest Hook 是高風險能力:只自動處理極小、明確、低風險的許可權項。其他保持 ask。

17. PermissionDenied:讓 auto mode 重試

PermissionDenied 在 auto mode 分類器拒絕工具呼叫時觸發。

某些情況下,你可以返回:

{
  "retry": true
}

告訴模型這次可以重試。

適合:

  • 分類器誤拒絕了低風險操作。
  • 你有額外指令碼確認這次行為安全。

不適合:

  • 用來繞過真實風險。
  • 對高風險 Bash / Edit / MCP 寫操作統一 retry。

retry 不是 allow all:它只是讓模型可重試被 auto mode 拒絕的呼叫。仍要窄匹配、可解釋。

18. Prompt-based hooks

Prompt hook 用 LLM 做判斷,適合輸入資料本身就足夠判斷的場景。

示例:Stop 時檢查任務是否完成。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "Check if all requested tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains to be done\"}."
          }
        ]
      }
    ]
  }
}

輸出協議:

  • {"ok": true}:繼續結束。
  • {"ok": false, "reason": "..."}:根據事件執行阻斷或反饋。

適合:

  • 檢查回答是否覆蓋使用者請求。
  • 判斷 summary 是否缺關鍵資訊。
  • 基於 hook input 做輕量評估。

不適合:

  • 需要讀檔案、跑測試、查日誌。
  • 生產級確定性規則。

Prompt hook 是判斷,不是執行:需要實際查程式碼狀態時用 agent hook 或 command hook。

19. Agent-based hooks

Agent hook 會 spawn subagent。官方標註為 experimental,生產工作流優先用 command hooks。

適合:

  • 需要讀檔案。
  • 需要搜尋程式碼。
  • 需要跑命令驗證。
  • 單次 prompt 判斷不夠。

示例:Stop 前檢查測試是否透過。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Verify that all unit tests pass. Run the test suite and check the results. $ARGUMENTS",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

邊界:

  • 預設 timeout 比 prompt hook 長。
  • 最多會使用一定數量的 tool-use turns。
  • 更貴、更慢、更復雜。

Agent hook 不適合替代 CI:它可以做本地預檢,但真正釋出前仍要依賴可重複的測試和 CI。

20. HTTP hooks

HTTP hook 適合把事件傳送到外部系統:

  • Slack。
  • Discord。
  • 內部 webhook。
  • 審計平臺。
  • 任務系統。
  • 監控系統。

適合做通知和記錄,不適合直接做高風險生產操作。

安全要點:

  • 使用 HTTPS。
  • token 放在環境變數或憑據系統。
  • 不要把完整 prompt、完整 diff、金鑰、路徑隱私發出去。
  • 給外部系統加最小許可權。
  • 設定合理 timeout。

HTTP hook 是資料外發通道:預設只傳送必要欄位。不要把 transcript 或 tool input 原樣推到第三方。

21. Async hooks

Async hook 適合不需要阻塞 Claude 的後臺動作。

適合:

  • 傳送通知。
  • 寫審計日誌。
  • 上傳指標。
  • 非同步觸發慢任務。

不適合:

  • 阻止工具呼叫。
  • 決定是否繼續。
  • 必須在下一步前完成的檢查。

阻斷用同步,旁路用 async:需要影響當前流程就不要非同步。

22. Subagent hooks

Subagent 可以在自己的 frontmatter 裡定義 hooks。

示例:

---
name: db-reader
description: Execute read-only database queries.
tools: Bash
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/validate-readonly-query.sh"
---

You are a database analyst with read-only access.
Only execute SELECT queries.

Subagent hooks 的特點:

  • 只在這個 subagent 生命週期內生效。
  • agent 作為 subagent 或透過 --agent 作為 main session 時都會執行。
  • plugin-shipped agents 不支援 hooksmcpServerspermissionMode 這幾個欄位。

Project settings 也有:

  • SubagentStart
  • SubagentStop

適合做 setup / cleanup。

Subagent 工具邊界粗,Hook 邊界細:例如只允許 SELECT,要用 PreToolUse 校驗 Bash 內容。

23. Hooks 和 permission modes

Hooks 與 permission modes 是兩層不同機制。

  • PreToolUse 在工具呼叫前觸發,可以阻斷。
  • PermissionRequest 在許可權彈出視窗前觸發,可以自動回答某些請求。
  • PermissionDenied 可以處理 auto mode 拒絕。
  • bypassPermissions 仍然會影響許可權提示出現與否。

不要假設 Hook 可以解決所有模式差異。尤其是:

  • auto mode 下部分請求可能先被分類器拒絕。
  • background subagents 預批准後會 auto-deny 未批准項。
  • managed settings 可能停用 bypass 或強制策略。

Hook 不是許可權系統替代品:許可權邊界寫 permissions;Hook 用來補自動化、反饋、審計和複雜條件判斷。

24. Hook 合併和執行模型

官方說明:事件觸發時,所有匹配 hooks 會並行執行。相同命令會自動去重。

這帶來幾個後果:

  • 多個 scope 的 hooks 可能都會執行。
  • 不要假設 hooks 按順序序列。
  • 如果一個 hook 依賴另一個 hook 的輸出,設計就不穩。
  • 要序列流程,就寫成一個指令碼內部步驟。
  • Hook 輸出越多,越可能汙染上下文。

Hook 要獨立冪等:同一事件下每個 Hook 都應該能獨立執行,多次執行也不破壞狀態。

25. 安全邊界

Hook 是自動執行命令,所以風險很真實。

預設原則:

  • 不要在 Hook 裡刪除檔案。
  • 不要自動釋出生產。
  • 不要自動 git push
  • 不要把金鑰寫到日誌。
  • 不要把完整 transcript 發給外部服務。
  • 不要對許可權請求做寬 matcher allow。
  • 不要執行儲存庫裡不可信指令碼,除非已信任 workspace。
  • Project Hook 進 git 前要 review。
  • Managed Hook 應由管理員維護。

指令碼建議:

  • shell 指令碼首行使用 shebang。
  • 使用 set -euo pipefail
  • jq 解析 JSON,避免字串硬拆。
  • 對路徑做白名單或嚴格匹配。
  • 出錯時 stderr 寫明確原因。
  • 對網路呼叫設定 timeout。

Hook 的能力取決於本機許可權:Claude Code 能執行什麼,Hook 就可能執行什麼。不要把不可信 Hook 當普通提示詞看待。

26. 常見故障:Hook 不觸發

按這個順序查:

  1. 執行 /hooks,確認事件下有配置。
  2. 檢查寫入的是正確 settings scope。
  3. 確認 JSON 沒有被另一個 hooks key 覆蓋。
  4. 檢查 event name 拼寫。
  5. 檢查 matcher 是否匹配事件物件。
  6. 如果用了 if,確認 Claude Code 版本至少 v2.1.85。
  7. 確認指令碼有執行許可權。
  8. 檢查指令碼路徑,優先用 $CLAUDE_PROJECT_DIR
  9. 開啟 verbose / debug log 看 stderr。

先寫最小 echo hook:確認事件能觸發,再逐步加 matcher、if、指令碼邏輯。

27. 常見故障:Hook 報 JSON validation failed

常見原因:

  • stdout 輸出了非 JSON 內容,但事件期待 JSON。
  • prompt hook 返回的不是 {"ok": true}{"ok": false, "reason": "..."}
  • command hook 同時寫了 JSON 和普通日誌到 stdout。
  • exit code 2 時還期待 stdout JSON 生效。
  • JSON 引號被 shell 轉義破壞。

修法:

  • 普通日誌寫 stderr。
  • stdout 只寫機器要讀的 JSON。
  • jq -n 生成 JSON,減少轉義錯誤。
  • 需要阻斷就 exit 2 + stderr,不要混 JSON。

stdout 是協議通道:不要隨便 echo debug 到 stdout。除錯日誌寫 stderr 或檔案。

28. 常見故障:Stop hook 無限迴圈

Stop hook 如果一直返回 ok: false,Claude 會繼續工作,然後再次觸發 Stop。

常見原因:

  • 判定條件過於理想化。
  • reason 不可執行。
  • Claude 無法滿足 Hook 要求。
  • Hook 沒有最大嘗試次數。

修法:

  • reason 寫具體下一步。
  • 增加外部狀態計數。
  • 只檢查本輪使用者明確要求。
  • 複雜驗證交給 CI,不要用 Stop hook 無限追求完美。

Stop hook 要能收斂:它應該推動完成,不應該把會話鎖進無法滿足的迴圈。

29. 常見故障:Hook 影響太大

表現:

  • 所有 Bash 都被檢查,速度變慢。
  • 所有許可權都被自動批准。
  • 每次編輯都跑整個測試套件。
  • 大量 stdout 被塞進上下文。
  • 多個 Hook 並行寫同一個檔案導致日誌亂序。

處理:

  • 縮小 matcher。
  • if
  • 用檔案路徑篩選。
  • 把慢任務改 async 或 CI。
  • stdout 限制在必要內容。
  • 日誌寫檔案並加鎖。

Hook 要窄、快、可解釋:越靠近高頻事件,越要剋制。

30. 推薦落地順序

不要一上來做複雜 Hook 系統。按這個順序:

  1. User scope 加 Notification。
  2. Project scope 加格式化 Hook。
  3. Project scope 加敏感檔案保護。
  4. 給 Bash / MCP 高風險呼叫加 PreToolUse 校驗。
  5. 需要時加 ConfigChange 審計。
  6. 複雜專案再加 CwdChanged / FileChanged 環境過載。
  7. 只對低風險、明確事件加 PermissionRequest 自動處理。
  8. 最後再考慮 prompt / agent / HTTP / async hooks。

先從低風險、可觀察開始:通知和格式化最適合入門;自動批准和生產動作最晚考慮。

31. 自檢清單

學完這一章,你應該能做到:

  • 我能解釋 Hook 和 prompt / Skill 的區別。
  • 我知道 Hook 配在 settings 的 hooks block 裡。
  • 我能用 /hooks 檢視當前配置。
  • 我知道 PreToolUsePostToolUsePermissionRequestNotificationStop 的用途。
  • 我知道 matcher 匹配物件隨事件變化。
  • 我知道 if 使用 permission rule syntax。
  • 我知道 exit 0、exit 2 和其他 exit code 的差別。
  • 我知道 structured JSON 輸出必須走 stdout。
  • 我知道 command、prompt、agent、HTTP、async hooks 的取捨。
  • 我知道 Hook 不是 permissions 的替代品。
  • 我知道自動批准許可權請求必須極窄。
  • 我知道怎麼排查 hook 不觸發和 JSON validation failed。

32. 術語速查

  • Hook:Claude Code 生命週期事件上的自動化動作。
  • Command hook:執行本機 shell 命令的 Hook。
  • Prompt hook:用 LLM 根據 hook input 做判斷的 Hook。
  • Agent hook:spawn subagent 做驗證的 Hook。
  • HTTP hook:把事件發到 HTTP endpoint 的 Hook。
  • Async hook:後臺執行、不阻塞當前流程的 Hook。
  • matcher:按事件物件篩選 hook group。
  • if:按工具名和引數進一步篩選工具類 hook。
  • PreToolUse:工具執行前事件。
  • PostToolUse:工具成功執行後事件。
  • PermissionRequest:許可權彈出視窗出現前事件。
  • PermissionDenied:auto mode 拒絕工具呼叫後事件。
  • Notification:Claude Code 傳送通知時事件。
  • Stop:Claude 完成響應時事件。
  • ConfigChange:配置變化事件。
  • CwdChanged:工作目錄變化事件。
  • FileChanged:監聽檔案變化事件。
  • CLAUDE_ENV_FILE:Claude Code Bash preamble 環境檔案。
  • hookSpecificOutput:structured JSON 輸出裡的事件專屬控制欄位。

33. 官方資料

本頁目錄