---
title: "使用 Hooks"
description: "Claude Code Hooks 在生命週期事件上執行確定性自動化。學會事件、matcher、if、exit code、JSON 輸出、command/prompt/agent/HTTP hooks 和安全邊界。"
canonical: "https://xiangyugongzuoliu.com/tag/claude-code"
verifiedAt: "2026-05-05"
---

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

> [!NOTE]
> **這一章用 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 是事件發生時直接執行。

> [!IMPORTANT]
> **第一性原理**：需要推理和自由裁量，用 Skill 或 Subagent；需要每次固定執行，用 Hook；需要安全硬邊界，優先 permissions + Hook。

```mermaid
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 給反饋。

> [!WARNING]
> **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 生命週期內生效

基礎結構：

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

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

檢視入口：

```text
/hooks
```

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

> [!TIP]
> **Project Hook 適合團隊規則**；User Hook 適合個人通知和日誌；Local Hook 適合本機路徑；Managed Hook 適合組織強制自動化。

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

macOS 示例：

```json
{
  "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 跑一次：

```bash
osascript -e 'display notification "test"'
```

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

> [!TIP]
> **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 前後。
- `InstructionsLoaded`：`CLAUDE.md` 或 rules 載入到上下文。
- `Elicitation` / `ElicitationResult`：MCP elicitation 相關事件。

> [!IMPORTANT]
> **事件決定能力邊界**：阻斷工具要用 `PreToolUse`；工具完成後格式化用 `PostToolUse`；許可權彈出視窗自動處理用 `PermissionRequest`；上下文補充用 `SessionStart` 或 `PostCompact`。

## 6. Hook 怎麼收到輸入

Command hook 會從 stdin 收到 JSON。

常見欄位包括：

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

例如 `PreToolUse` / `PostToolUse` 裡，通常會有：

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

指令碼里一般用 `jq` 解析：

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

> [!TIP]
> **先儲存輸入樣本**：寫複雜 Hook 前，先把 stdin JSON append 到臨時日誌，確認欄位名和事件結構。

## 7. Exit code 語義

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

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

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

```bash
#!/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
```

> [!WARNING]
> **不要混用 exit 2 和 JSON allow/deny**：官方說明 exit 2 時 JSON 會被忽略。要結構化控制，用 exit 0 + stdout JSON。

## 8. Structured JSON 輸出

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

`PreToolUse` 可以返回：

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

效果：

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

`PermissionRequest` 可以返回 allow：

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

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

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

> [!CAUTION]
> **自動 allow 要極窄**：`PermissionRequest` matcher 不要寫空或 `.*`，否則可能自動批准檔案寫入、shell 命令和外部工具。

## 9. matcher 怎麼工作

`matcher` 用來篩選某類事件。

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

- `PreToolUse` / `PostToolUse`：工具名，例如 `Bash`、`Edit`、`Write`、`mcp__github__.*`。
- `Notification`：通知型別，例如 `permission_prompt`、`idle_prompt`。
- `SubagentStart` / `SubagentStop`：agent type，例如 `Explore`、`Plan`、custom agent name。
- `PreCompact` / `PostCompact`：`manual` 或 `auto`。
- `ConfigChange`：`user_settings`、`project_settings`、`local_settings`、`policy_settings`、`skills`。
- `FileChanged`：要監聽的 literal filenames，例如 `.envrc|.env`。
- `SessionEnd`：結束原因，例如 `clear`。

一些事件不支援 matcher，會每次觸發：

- `UserPromptSubmit`
- `PostToolBatch`
- `Stop`
- `TaskCreated`
- `TaskCompleted`
- `CwdChanged`
- `WorktreeCreate`
- `WorktreeRemove`

> [!TIP]
> **先用 matcher 粗篩**：例如 `Edit|Write`。再用 `if` 或指令碼內部邏輯做細篩。

## 10. `if` 欄位：按工具名和引數過濾

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

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

例子：只檢查 git 命令，而不是所有 Bash。

```json
{
  "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 執行。

> [!IMPORTANT]
> **`matcher` 按事件物件過濾，`if` 按工具和引數過濾**。需要按 Bash 子命令、Edit 路徑、MCP tool 細分時，用 `if`。

## 11. PostToolUse：編輯後自動格式化

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

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

這個 Hook 的執行邏輯：

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

邊界：

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

> [!TIP]
> **格式化適合 PostToolUse**：不要讓 Claude 自己記住“改完跑 formatter”。讓 Hook 負責。

## 12. PreToolUse：阻止受保護檔案

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

```bash
#!/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
```

註冊：

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

指令碼要可執行：

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

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

## 13. SessionStart / PostCompact：注入上下文

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

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

```json
{
  "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、環境狀態。
- 不要注入大段內容，避免上下文汙染。

> [!TIP]
> **靜態規則進 `CLAUDE.md`，動態上下文用 Hook**。

## 14. ConfigChange：審計配置變化

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

示例：記錄配置變化。

```json
{
  "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。

> [!WARNING]
> **審計日誌不要寫敏感值**：只記錄檔案、來源、時間和動作，避免把 token、路徑隱私和賬號寫進日誌。

## 15. CwdChanged / FileChanged：過載環境

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

`SessionStart` + `CwdChanged` 示例：

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

監聽特定檔案：

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

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

> [!CAUTION]
> **載入 `.env` 要小心**：不要把敏感值列印到 stdout。環境注入寫 `CLAUDE_ENV_FILE`，不要回顯到對話。

## 16. PermissionRequest：自動批准要極窄

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

示例：只自動批准 `ExitPlanMode`。

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

不要這樣做：

- 空 matcher 自動批准所有請求。
- `.*` 自動批准所有請求。
- 對 `Bash`、`Edit`、MCP 寫操作做無條件 allow。

> [!IMPORTANT]
> **PermissionRequest Hook 是高風險能力**：只自動處理極小、明確、低風險的許可權項。其他保持 ask。

## 17. PermissionDenied：讓 auto mode 重試

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

某些情況下，你可以返回：

```json
{
  "retry": true
}
```

告訴模型這次可以重試。

適合：

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

不適合：

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

> [!WARNING]
> **retry 不是 allow all**：它只是讓模型可重試被 auto mode 拒絕的呼叫。仍要窄匹配、可解釋。

## 18. Prompt-based hooks

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

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

```json
{
  "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 做輕量評估。

不適合：

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

> [!TIP]
> **Prompt hook 是判斷，不是執行**：需要實際查程式碼狀態時用 agent hook 或 command hook。

## 19. Agent-based hooks

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

適合：

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

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

```json
{
  "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。
- 更貴、更慢、更復雜。

> [!WARNING]
> **Agent hook 不適合替代 CI**：它可以做本地預檢，但真正釋出前仍要依賴可重複的測試和 CI。

## 20. HTTP hooks

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

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

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

安全要點：

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

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

## 21. Async hooks

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

適合：

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

不適合：

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

> [!TIP]
> **阻斷用同步，旁路用 async**：需要影響當前流程就不要非同步。

## 22. Subagent hooks

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

示例：

```markdown
---
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 不支援 `hooks`、`mcpServers`、`permissionMode` 這幾個欄位。

Project settings 也有：

- `SubagentStart`
- `SubagentStop`

適合做 setup / cleanup。

> [!IMPORTANT]
> **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 或強制策略。

> [!WARNING]
> **Hook 不是許可權系統替代品**：許可權邊界寫 permissions；Hook 用來補自動化、反饋、審計和複雜條件判斷。

## 24. Hook 合併和執行模型

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

這帶來幾個後果：

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

> [!TIP]
> **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。

> [!CAUTION]
> **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。

> [!TIP]
> **先寫最小 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。

> [!WARNING]
> **stdout 是協議通道**：不要隨便 `echo debug` 到 stdout。除錯日誌寫 stderr 或檔案。

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

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

常見原因：

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

修法：

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

> [!CAUTION]
> **Stop hook 要能收斂**：它應該推動完成，不應該把會話鎖進無法滿足的迴圈。

## 29. 常見故障：Hook 影響太大

表現：

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

處理：

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

> [!IMPORTANT]
> **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。

> [!TIP]
> **先從低風險、可觀察開始**：通知和格式化最適合入門；自動批准和生產動作最晚考慮。

## 31. 自檢清單

學完這一章，你應該能做到：

- 我能解釋 Hook 和 prompt / Skill 的區別。
- 我知道 Hook 配在 settings 的 `hooks` block 裡。
- 我能用 `/hooks` 檢視當前配置。
- 我知道 `PreToolUse`、`PostToolUse`、`PermissionRequest`、`Notification`、`Stop` 的用途。
- 我知道 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. 官方資料

- [Automate workflows with hooks](https://code.claude.com/docs/en/hooks-guide)
- [Hooks reference](https://code.claude.com/docs/en/hooks)
- [Create custom subagents](https://code.claude.com/docs/en/sub-agents)
- [Extend Claude Code](https://code.claude.com/docs/en/features-overview)
- [Extend Claude with skills](https://code.claude.com/docs/en/skills)
- [Configure permissions](https://code.claude.com/docs/en/permissions)

<Cards>
  <Card href="/zh-Hant/docs/claude-code/official/02-extensions-automation/commands" title="Commands" description="Hook 是事件自動化；Commands 是會話內控制入口。下一章講內建命令、bundled skills 和 MCP prompts。" />
  <Card href="/zh-Hant/docs/claude-code/official/02-extensions-automation/subagents" title="Subagents（上一篇）" description="Agent hook 和 Subagent hooks 都依賴 subagent 機制。需要回看隔離 worker 的工具、許可權和 memory 邊界。" />
  <Card href="/zh-Hant/docs/claude-code/official/01-core-capabilities/permissions" title="管理許可權" description="Hook 能補充複雜條件和反饋，但真正的工具邊界仍應寫在 permissions 中。" />
</Cards>
