> ## Documentation Index
> Fetch the complete documentation index at: https://felimet-hub.jmcores.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 04-6 Hooks：是什麼、用途、各觸發點

> Hook 把「每次都要記得做」從模型軟約束變成系統硬保證。本單元講 30 個生命週期事件、settings.json 配置、exit code 語義、五種 handler 類型，以及 hook 作為可執行碼的供應鏈風險與掃描 SOP。

export const LearnerPrimer = ({items = [], lang = "zh"}) => {
  const t = lang === "en" ? {
    title: "Before this unit, be honest with yourself",
    sub: "If you can't answer these, that gap is exactly what this unit closes."
  } : {
    title: "讀這個單元前，先誠實面對",
    sub: "這幾題答不出來，正是這個單元要替你補的洞。"
  };
  const css = `
  .lp-root{--lp-bg:#FAF7F1;--lp-surface:rgba(191,117,81,0.05);--lp-border:rgba(0,0,0,0.09);--lp-edge:rgba(191,117,81,0.42);--lp-text:#2b2722;--lp-dim:#6f6a62;--lp-accent:#bf7551;border:1px solid var(--lp-border);border-left:3px solid var(--lp-edge);border-radius:13px;background:var(--lp-bg);color:var(--lp-text);overflow:hidden;margin:1.25rem 0;}
  .dark .lp-root{--lp-bg:#1b1a18;--lp-surface:rgba(207,138,104,0.07);--lp-border:rgba(255,255,255,0.08);--lp-edge:rgba(207,138,104,0.5);--lp-text:#e7e3da;--lp-dim:#a8a299;--lp-accent:#cf8a68;}
  .lp-head{display:flex;align-items:center;gap:9px;padding:13px 18px 11px;border-bottom:1px solid var(--lp-border);background:var(--lp-surface);}
  .lp-ic{color:var(--lp-accent);flex-shrink:0;}
  .lp-htx{display:flex;flex-direction:column;gap:1px;min-width:0;}
  .lp-title{font-size:14px;font-weight:650;line-height:1.3;letter-spacing:.01em;}
  .lp-sub{font-size:12px;color:var(--lp-dim);line-height:1.4;}
  .lp-list{list-style:none;margin:0;padding:10px 18px 14px;display:flex;flex-direction:column;gap:0;}
  .lp-item{display:flex;align-items:baseline;gap:11px;padding:7px 0;font-size:14px;line-height:1.6;border-top:1px solid var(--lp-border);}
  .lp-item:first-child{border-top:none;}
  .lp-mark{flex-shrink:0;color:var(--lp-accent);font-size:13px;font-weight:700;line-height:1.55;font-variant-numeric:tabular-nums;opacity:.85;}
  .lp-q{flex:1 1 0;min-width:0;color:var(--lp-text);}
  @media (max-width:620px){.lp-head{padding:12px 14px 10px;}.lp-list{padding:8px 14px 12px;}}
  `;
  return <div className="lp-root">
      <style>{css}</style>
      <div className="lp-head">
        <svg className="lp-ic" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10" /><circle cx="12" cy="12" r="6" /><circle cx="12" cy="12" r="2" /></svg>
        <span className="lp-htx">
          <span className="lp-title">{t.title}</span>
          <span className="lp-sub">{t.sub}</span>
        </span>
      </div>
      <ul className="lp-list">
        {items.map((q, i) => <li className="lp-item" key={i}>
            <span className="lp-mark">{String(i + 1).padStart(2, "0")}</span>
            <span className="lp-q">{q}</span>
          </li>)}
      </ul>
    </div>;
};

<LearnerPrimer
  lang="zh"
  items={[
"Hook 是 shell 確定性，補「模型不一定會記得」的缺口。",
"exit 1 不會擋下工具呼叫，要擋下請用 exit 2。",
"PostToolUse 沒設 timeout，多次存檔後任務堆積消耗資源。",
"你從公開 repo 貼 hook 沒先掃，陌生程式碼在跑。",
"exit 2 攔截沒給原因，Claude 收到的只是純文字 stderr。",
"Hook 是可執行程式碼，安裝陌生 hook 等於把 shell 權限交出去。",
"「希望模型做」寫成 hook 跑 shell 層，浪費資源又徒勞。",
]}
/>

<Info>
  **這個單元解決什麼問題**

  Hook 是在工具執行前後、session 起訖、prompt 提交等 30 個生命週期事件點自動跑的確定性腳本：格式化、驗證、攔截、防護網。它把「每次都要記得做」的事從模型軟約束變成系統硬保證。Claude Code v2.1 起 hook 系統大幅擴充：30 個事件、五種 handler 類型（command / http / mcp\_tool / prompt / agent）、與 exit code 2 攔截語義。本單元講主要觸發點語義、`settings.json` 配置、實戰範本，以及 hook 作為可執行碼所附帶的供應鏈風險與掃描 SOP。
</Info>

## 學習目標

* [ ] 說出 Claude Code hook 的 30 個事件按觸發時機分組，並指出 PreToolUse、PostToolUse、Stop、SessionStart 的語義與典型用途。
* [ ] 正確撰寫 `settings.json` 的 `hooks` 設定，含 `matcher` 與 `command`，並能區分 `matcher` 的三種解析規則。
* [ ] 用 `exit 2` 攔截 PreToolUse 工具呼叫，以及用 `additionalContext` 為 Claude 注入額外 context。
* [ ] 辨識 hook 作為外部可執行碼的供應鏈風險，並在引入第三方 hook 前做基本掃描。
* [ ] 用 hook 把本庫的 `normalize_punctuation.py` 與 `validate_kb --hook` 接成確定性防護網。

***

## 1. Hook 是什麼：確定性自動化，補「不一定會記得」的缺口

`CLAUDE.md` 與 rules 是「請模型遵守」（軟約束、context 層）。Hook 是「系統強制執行」（硬保證、shell 層）。它**不在模型的推理鏈內**：hook 跑在你 shell 上、讀 stdin 上的工具輸入 JSON、決定要不要放行這次工具呼叫，模型預設不知道 hook 在跑。你用 `additionalContext` 主動告知時例外。

三個關鍵事實：

* **Hook 是確定性的**：不靠模型「記得」或「判斷」，shell 該 exit 2 就是 exit 2。每次都一樣。
* **Hook 是可執行程式碼**：裝 hook 等同於把一段 shell 指令加進你的工作流，這是供應鏈面的入口。
* **Hook 的觸發點遠比一般認得多**：截至 2026-06 共 30 個事件，按觸發節奏分四組 \[1]。

<Tip>
  **與 04-1 / 04-2 / 04-4 的分工**

  寫「不要 commit `.env`」在 `CLAUDE.md` 的話，模型多半會記，但偶爾漏。
  寫在 `.claude/rules/security.md`（path-scoped）的話，Claude 讀到相關檔案時收到約束，但仍只是 context。
  寫成 hook（PreToolUse on Bash，matcher `git commit *`）的話，shell 端檢查 staged 檔案含 `.env` 就 `exit 2` 攔截，**任何時候都擋下**。

  判準只有一句：**要「模型記得」還是「系統保證」？** 後者就必須走 hook。
</Tip>

## 2. 30 個觸發事件分組

Claude Code v2.1 起 hook 系統涵蓋 30 個事件（\[1]），按觸發節奏分四組。

### 2.1 每次 session 一次

| 事件             | 觸發時機                              | 典型用途                                        |
| -------------- | --------------------------------- | ------------------------------------------- |
| `SessionStart` | session 啟動、resume、clear、compact 後 | 注入動態 context、載入環境變數、確認前置條件、設 `sessionTitle` |
| `SessionEnd`   | session 結束                        | 清理資源、archive transcript                     |
| `Setup`        | 初始 setup 階段                       | 一次性初始化（不同於 SessionStart 每次都跑）               |

### 2.2 每次對話輪次一次

| 事件                    | 觸發時機                     | 典型用途                                         |
| --------------------- | ------------------------ | -------------------------------------------- |
| `UserPromptSubmit`    | 使用者送出 prompt 那一刻         | 注入 prefix context（環境狀態、提醒）、reject 並清除 prompt |
| `UserPromptExpansion` | prompt 擴展階段              | 阻擋 prompt expansion                          |
| `Stop`                | 主代理完成、`StopFailure` 失敗完成 | 收尾驗證（跑測試、靜態檢查）、發送完成通知                        |
| `PreCompact`          | compact 之前               | 阻擋 compact、把必須保留的 state 寫到 transcript        |
| `PostCompact`         | compact 之後               | 重新掛載必要 context                               |

### 2.3 每個工具呼叫一次（agentic loop 內）

| 事件                   | 觸發時機                     | 典型用途                                    |
| -------------------- | ------------------------ | --------------------------------------- |
| `PreToolUse`         | 工具執行之前                   | 驗證參數、阻擋危險指令、`permissionDecision` 改寫工具呼叫 |
| `PostToolUse`        | 工具成功執行之後                 | 自動 format、lint、型別檢查、log                 |
| `PostToolUseFailure` | 工具失敗之後                   | 失敗通知、附加除錯 context                       |
| `PostToolBatch`      | 工具批次結束，下一個 model call 之前 | 批次級攔截、結束 agentic loop                   |
| `PermissionRequest`  | 觸發權限 prompt 時            | 自動決策 allow/deny                         |
| `PermissionDenied`   | 使用者拒絕時                   | 重試或 fallback                            |

### 2.4 Subagent / task 事件

| 事件              | 觸發時機            | 典型用途                                              |
| --------------- | --------------- | ------------------------------------------------- |
| `SubagentStart` | Subagent 啟動     | 注入 subagent-specific context、log                  |
| `SubagentStop`  | Subagent 結束     | 收尾驗證（Subagent 的 `Stop` hook 會自動轉成 `SubagentStop`） |
| `TeammateIdle`  | Agent team 隊友閒置 | 編排邏輯、訊息路由                                         |
| `TaskCreated`   | 新 task 建立       | log、預設設定                                          |
| `TaskCompleted` | task 完成         | 通知、清理                                             |

### 2.5 異步 / side-channel

| 事件                                  | 觸發時機                   |
| ----------------------------------- | ---------------------- |
| `Notification`                      | 通知需要使用者注意時             |
| `MessageDisplay`                    | 訊息要顯示前（hook 可改寫顯示內容）   |
| `ConfigChange`                      | settings 變更            |
| `CwdChanged`                        | 工作目錄變更                 |
| `FileChanged`                       | 檔案外部變更                 |
| `WorktreeCreate` / `WorktreeRemove` | git worktree 建立 / 移除   |
| `InstructionsLoaded`                | CLAUDE.md / rules 載入完成 |
| `Elicitation` / `ElicitationResult` | 需要使用者輸入時               |

<Warning>
  **本單元聚焦四個高頻事件**

  30 個事件全部講會失焦。實務上 80% 的 hook 用在 PreToolUse、PostToolUse、Stop、SessionStart 這四個，其他事件留到遇到具體需求再查文件 \[1]。
</Warning>

## 3. 配置：`settings.json` 結構

Hook 設定在 `settings.json` 的 `hooks` 區塊，三層巢狀：**事件 → matcher group → hook handlers**（\[1]）。

```json theme={null}
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh",
            "args": []
          }
        ]
      }
    ]
  }
}
```

### 3.1 五種 handler 類型

`type` 欄位決定 handler 怎麼跑（\[1]）：

| 類型         | 怎麼跑                            | 典型用途                      |
| ---------- | ------------------------------ | ------------------------- |
| `command`  | shell 腳本，stdin/stdout          | 90% 的場景，格式驗證、log、lint     |
| `http`     | POST 到 URL，body 是 hook 輸入 JSON | 外部系統整合（CI、Slack webhooks） |
| `mcp_tool` | 叫用已連線的 MCP tool                | 跨 hook 共用 MCP 能力          |
| `prompt`   | 單輪 LLM 評估                      | 需 LLM 判斷但不想跑整個 agent      |
| `agent`    | 跑一個 subagent + tools（**實驗性**）  | 需要工具的 LLM 評估              |

預設 `type` 為 `command`，省略 `type` 等同顯式指定 `command`。

### 3.2 設定位置與 scope

| 路徑                            | 範疇                                       |
| ----------------------------- | ---------------------------------------- |
| `~/.claude/settings.json`     | 所有專案（個人）                                 |
| `.claude/settings.json`       | 當前專案（進版控）                                |
| `.claude/settings.local.json` | 當前專案（加進 `.gitignore`）                    |
| Managed policy                | 組織全機器（`allowManagedHooksOnly` 可擋其他 hook） |
| Plugin `hooks/hooks.json`     | 該 plugin 啟用時                             |
| Skill / Subagent frontmatter  | 該 Skill / Subagent 生命週期內                 |

<Warning>
  **Managed 層級優先**

  組織可用 managed setting 設 `allowManagedHooksOnly: true` 阻擋使用者 / 專案 / plugin 層 hook。Plugin 透過 `enabledPlugins` 強制啟用的不受影響 \[1]。
</Warning>

### 3.3 matcher 語法

`matcher` 解析規則按字元自動選擇（\[1]）：

| Pattern          | 解析為              | 範例                            |
| ---------------- | ---------------- | ----------------------------- |
| `"*"`、`""`、省略    | match all        | 每次都觸發                         |
| 只含字母、數字、`_`、`\|` | 精確或 `\|` 清單      | `Bash`、`Edit\|Write`          |
| 其他字元             | JavaScript regex | `^Notebook`、`mcp__memory__.*` |

**每個事件 match 不同欄位**：

* 工具事件（PreToolUse / PostToolUse 等）match `tool_name`
* `SessionStart` match 來源（`startup` / `resume` / `clear` / `compact`）
* `Notification` match 類型
* `FileChanged` 是字面檔名（非 regex / 非 exact-list）
* 沒有 matcher 機制的事件（UserPromptSubmit、PostToolBatch、Stop、TeammateIdle、TaskCreated、TaskCompleted、WorktreeCreate、WorktreeRemove、MessageDisplay、CwdChanged）：always fire 或 always ignored if matcher set

<Warning>
  **MCP tool matcher 必須含 `.*`**

  `mcp__memory__.*` 匹配所有 memory 工具；`mcp__memory` 當作精確字串、match 不到東西 \[1]。
</Warning>

### 3.4 第二層篩選：`if`

`if` 欄位是 permission-rule 語法，提供更細的條件過濾（\[1]）：

```json theme={null}
{
  "matcher": "Bash",
  "hooks": [
    {
      "type": "command",
      "if": "Bash(git commit *)",
      "command": "..."
    }
  ]
}
```

支援的規則格式如 `"Bash(rm *)"`、`"Edit(*.ts)"`。**不支援 `&&` / `||` / 清單組合**，要多條件時拆成多個 handler。

<Warning>
  **`if` 只在工具事件生效**

  僅 PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied 會評估 `if`。其他事件若有 `if` 一律不跑 \[1]。
</Warning>

## 4. 輸入：stdin 上的 JSON

所有 `command` hook 從 stdin 收到統一格式的 JSON（\[1]）；`http` hook 收到 POST body。

基本 shape：

```json theme={null}
{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
  "cwd": "/home/user/my-project",
  "permission_mode": "default",
  "effort": { "level": "high" },
  "hook_event_name": "PreToolUse"
}
```

工具事件附加：

```json theme={null}
{
  "tool_name": "Bash",
  "tool_input": { "command": "npm test" }
}
```

`--agent` 或 Subagent 內執行時，會附加 `agent_id` 與 `agent_type`。**只有 `SessionStart` 會收到 `model` 欄位** \[1]。

<Tip>
  **從 stdin 取參數**

  Bash 範例（用 `jq` 從 stdin 解出欄位）：

  ```bash theme={null}
  FILE=$(jq -r '.tool_input.file_path // empty')
  if [ -z "$FILE" ]; then
    exit 0
  fi
  prettier --write "$FILE"
  ```
</Tip>

### 4.1 路徑佔位符

用 `args` exec 形式避免 quoting 問題（\[1]）：

* `${CLAUDE_PROJECT_DIR}`：專案根
* `${CLAUDE_PLUGIN_ROOT}`：plugin 安裝目錄
* `${CLAUDE_PLUGIN_DATA}`：plugin 持久資料
* `${user_config.*}`：plugin 使用者設定

## 5. Exit code 與決策

`command` hook 透過 exit code 表態（\[1]）：

| Exit  | 意義             | 效果                                                                                                           |
| ----- | -------------- | ------------------------------------------------------------------------------------------------------------ |
| **0** | 成功             | 解析 stdout 為 JSON；對 `UserPromptSubmit` / `UserPromptExpansion` / `SessionStart`，純 stdout 當作 context 加給 Claude |
| **2** | blocking error | stderr 餵回 Claude；阻擋動作（效果依事件而異）                                                                               |
| 其他    | 非阻斷錯誤          | transcript 顯示 `<hook name> hook error` + stderr 第一行；**繼續執行**                                                 |

### 5.1 `exit 2` 阻擋效果一覽

不是所有事件都把 `exit 2` 視為阻擋。真正會擋下的事件（\[1]）：

* `PreToolUse` → 阻擋工具呼叫
* `UserPromptSubmit` → reject 並清除 prompt
* `UserPromptExpansion` → 阻擋 expansion
* `Stop` / `SubagentStop` / `TeammateIdle` → 防止停止
* `PreCompact` → 阻擋 compact
* `PostToolBatch` → 在下個 model call 前停止 agentic loop
* `WorktreeCreate` → 任何非零 exit 都 fail creation

**`exit 2` 對 `PostToolUse` / `PostToolUseFailure` / `Notification` 等無阻擋效果**，只顯示 stderr。多數 Unix 直覺是「exit 1 是錯誤」，但 Claude Code hook 系統把 `exit 1` 當非阻斷錯誤。要強制擋下請用 `exit 2`。

### 5.2 為 Claude 注入額外 context

回傳 JSON 帶 `hookSpecificOutput.additionalContext` 給 Claude 看（需有對應的 `hookEventName`）（\[1]）：

```json theme={null}
{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
  }
}
```

注入位置依事件：

* `SessionStart` / `Setup` / `SubagentStart` → 對話開頭
* `UserPromptSubmit` / `UserPromptExpansion` → 跟著 prompt
* `PreToolUse` / `PostToolUse*` / `PostToolBatch` → 跟著工具結果

**上限 10,000 字元**；超長值寫到檔案、附 preview。多個 hook 的值都會注入。**用陳述句，不要用指令句**（避免觸發 prompt injection 防禦）\[1]。

### 5.3 通用 JSON 輸出欄位

| 欄位                    | 用途                                                 |
| --------------------- | -------------------------------------------------- |
| `continue`（預設 `true`） | 設 `false` 完全停掉 Claude                              |
| `stopReason`          | 停掉時給使用者看的訊息                                        |
| `suppressOutput`      | 從 transcript 隱藏 stdout                             |
| `systemMessage`       | 給使用者的 warning                                      |
| `terminalSequence`    | 發出 allowlist 內的 OSC / BEL escape（取代直接寫 `/dev/tty`） |

### 5.4 決策控制模式（不同事件用不同欄位）

| 事件群                                                                  | 模式                   | 關鍵欄位                                                                                |
| -------------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------- |
| `UserPromptSubmit`、`PostToolUse*`、`Stop`、`PreCompact`、`ConfigChange` | top-level `decision` | `decision: "block"`、`reason`                                                        |
| `PreToolUse`                                                         | `hookSpecificOutput` | `permissionDecision: allow/deny/ask/defer`、`permissionDecisionReason`               |
| `PermissionRequest`                                                  | `hookSpecificOutput` | `decision.behavior: allow/deny`、`updatedInput`、permission rules                     |
| `PermissionDenied`                                                   | `hookSpecificOutput` | `retry: true`                                                                       |
| `WorktreeCreate`                                                     | stdout path          | 或 `hookSpecificOutput.worktreePath`                                                 |
| `Elicitation` / `ElicitationResult`                                  | `hookSpecificOutput` | `action: accept/decline/cancel`、`content`                                           |
| `MessageDisplay`                                                     | `hookSpecificOutput` | `displayContent` 改寫螢幕顯示                                                             |
| `SessionStart`                                                       | `hookSpecificOutput` | `additionalContext`、`initialUserMessage`、`watchPaths`、`sessionTitle`、`reloadSkills` |

## 6. 五個實戰範本

### 6.1 PreToolUse 攔截危險 Bash

```bash theme={null}
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Destructive command blocked by hook"
    }
  }'
else
  exit 0
fi
```

對應 `settings.json`：

```json theme={null}
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}
```

<Tip>
  **為什麼用 `permissionDecision: deny` 而不是 `exit 2`**

  對 `PreToolUse` 來說，**回 JSON 帶 `permissionDecision` 比 `exit 2` 更乾淨**：可以附帶 `permissionDecisionReason` 給 Claude 看、可以選擇 `deny`（明確拒絕）、`allow`（明確允許跳過 prompt）、`ask`（回到使用者確認）、`defer`（回到正常流程）。`exit 2` 只能用 stderr 表態，不能表達 allow \[1]。
</Tip>

### 6.2 PostToolUse 自動格式化（本庫用法）

```json theme={null}
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python ${CLAUDE_PROJECT_DIR}/.claude/scripts/normalize_punctuation.py"
          }
        ]
      }
    ]
  }
}
```

<Note>
  **路徑從 stdin 取，不靠帶路徑的環境變數**

  上例的腳本自行掃描待處理檔案。若要對「剛編輯的單檔」處理，從 stdin 的 hook 輸入 JSON 解出 `.tool_input.file_path`（見第 4 節的 `jq` 範例）。Claude Code **不提供** `$CLAUDE_TOOL_INPUT_FILE_PATH` 這類帶路徑的環境變數（截至 2026-06）；可在 `command` 字串內展開的是 `${CLAUDE_PROJECT_DIR}`、`${CLAUDE_PLUGIN_ROOT}`、`${CLAUDE_PLUGIN_DATA}` 等定址變數 \[1]。
</Note>

<Warning>
  **PostToolUse 必設 timeout 與增量**

  沒設 timeout 時，PostToolUse 預設 600 秒上限 \[1]。但快速連續編輯時，多個 hook 會疊加執行、消耗資源。本庫的 `normalize_punctuation.py` 對單檔處理是輕量，但重型 hook（tsc、ruff --fix）務必：

  * 設 `timeout` 欄位（30-60 秒）防止 hang
  * 用 `--incremental` 等增量旗標
  * Windows 上若依賴 GNU `timeout`，改用 PowerShell 包裝（`Start-Process -TimeoutSec`）
</Warning>

### 6.3 Stop 跑收尾驗證（本庫用法）

```json theme={null}
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "python ${CLAUDE_PROJECT_DIR}/.claude/scripts/validate_kb.py --hook"
          }
        ]
      }
    ]
  }
}
```

`validate_kb.py --hook` 是 Stop hook 模式：**有 CRITICAL/HIGH 才擋下、工具錯誤 fail-open 不誤傷正常流程**。它在本庫作為確定性防護網，**即使自然語言驅動的自主檢核漏跑，回合結束時仍會擋下**。

### 6.4 SessionStart 注入環境變數

SessionStart hook 可透過 `$CLAUDE_ENV_FILE` 環境變數（指向 session 專屬暫存檔）持久化環境變數 \[1]：

```bash theme={null}
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
  echo 'export CUSTOM_FLAG=1' >> "$CLAUDE_ENV_FILE"
fi
exit 0
```

這是 Claude Code 設定環境變數的**官方支援方式**（截至 2026-06 僅 `SessionStart` / `Setup` / `CwdChanged` / `FileChanged` 可用）。

### 6.5 工具呼叫 log（PostToolUse）

```bash theme={null}
#!/bin/bash
# .claude/hooks/log-tool.sh
jq -c '{
  ts: now | todate,
  session_id: .session_id,
  tool: .tool_name,
  input: .tool_input
}' >> "${CLAUDE_PROJECT_DIR}/.claude/logs/tools.jsonl"
exit 0
```

最小欄位符合 [01-4 上下文工程](/code-agent/foundations/context-engineering) 提到的審查需求：timestamp、session\_id、tool、input。可事後回放、稽核、或喂給 [04-4 Skill](/code-agent/customization/skills) 做 pattern 分析。

<Tip>
  **背景執行**

  加 `"async": true` 讓 hook 不阻塞主流程（\[1]）：

  ```json theme={null}
  { "type": "command", "command": "long-task.sh", "async": true }
  ```

  配 `"asyncRewake": true` 在 `exit 2` 時透過 system reminder 喚醒 Claude。
</Tip>

## 7. Skill / Subagent frontmatter 內的 hook

不只是 `settings.json`，**Skill 與 Subagent 的 frontmatter 內可宣告 hook**，只在該 Skill / Subagent 生命週期內生效（\[1]）：

```yaml theme={null}
---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---
```

<Tip>
  **Subagent 的 `Stop` 自動轉 `SubagentStop`**

  Subagent frontmatter 內寫的 `Stop` hook 會**自動轉成 `SubagentStop`**，不需要手動改 \[1]。這讓「subagent 完成時跑驗證」是 frontmatter 內一句話的事。
</Tip>

## 8. 進階：HTTP / MCP / prompt handler

### 8.1 HTTP handler 範本

```json theme={null}
{
  "type": "http",
  "url": "http://localhost:8080/hooks/pre-tool-use",
  "timeout": 30,
  "headers": { "Authorization": "Bearer $MY_TOKEN" },
  "allowedEnvVars": ["MY_TOKEN"]
}
```

`allowedEnvVars` 是白名單：只暴露列出的環境變數給 hook。**不要在 `headers` 直接寫值**（會進 plugin 設定檔），用 `$VAR` 引用環境變數 \[1]。

### 8.2 MCP tool handler 範本

```json theme={null}
{
  "type": "mcp_tool",
  "server": "my_server",
  "tool": "security_scan",
  "input": { "file_path": "${tool_input.file_path}" }
}
```

適合跨 hook 共用 MCP 能力，例如每個檔案編輯後跑 MCP 上的 security scan。

### 8.3 prompt handler 範本

```json theme={null}
{
  "type": "prompt",
  "prompt": "Review the tool call below and decide if it's safe. Respond with `allow` or `deny`."
}
```

需要 LLM 判斷、但不想跑整個 agent 時用。timeout 預設 30 秒 \[1]。

## 9. 安全：hook 是可執行碼，外部來源即供應鏈面

Hook 的 `command` 欄位**直接由 shell 執行**。安裝來路不明的 hook 等同於把第三方程式碼的執行權交出去。

### 9.1 已知威脅

* **CVE-2025-59536**（CVSS 8.7）：`.claude/` 內的程式碼在使用者核准信任對話前已執行。已於 Claude Code v1.0.111+ 修復 \[2]。未更新版本有被預置惡意指令的風險。
* **CVE-2026-21852**：攻擊者控制的 app 改寫 `ANTHROPIC_BASE_URL`，把 API 流量導向他處並在 trust 前外洩 API key。修於 v2.0.65+ \[2]。
* **Snyk ToxicSkills（2026-02）**：公開 skill 中 36% 含 prompt injection。Hook 與 skill 同屬供應鏈面，引入前先掃描。

### 9.2 引入外部 hook 前必跑的掃描

```bash theme={null}
# 1. 偵測 hook/skill 內的可疑外連指令
rg -n 'curl|wget|nc|ssh|ANTHROPIC_BASE_URL|enableAllProjectMcpServers' .claude/

# 2. 偵測隱藏 Unicode 控制字元（零寬、bidi 覆寫）
rg -nP '[\x{200B}-\x{200D}\x{202A}-\x{202E}]' .claude/

# 3. 偵測 HTML 註解、script tag、base64 blob
rg -n '<!--|<script|data:text/html|base64,' .claude/

# 4. 偵測可疑權限或外連
rg -n 'curl|wget|nc|scp|ssh|enableAllProjectMcpServers|ANTHROPIC_BASE_URL' .claude/
```

任何一項 hit 都要人工 review 上下文，不要直接裝。

### 9.3 最小權限原則

Hook 的 `command` 只授予完成任務所需的最小存取：

* **不要在 hook 內讀 `~/.ssh`、`~/.aws`、`**/.env*`**。這些路徑都該在 `permissions.deny` 阻擋。
* **不要讓 hook 寫到 repo 外**，用 `${CLAUDE_PROJECT_DIR}` 限定在專案內。
* **用 `allowedEnvVars` 白名單環境變數**（HTTP handler），不要把整個 process env 暴露。

### 9.4 版本與 managed 政策

* `disableAllHooks: true` 在 settings 可暫時關掉所有 hook（**managed 級 hook 只能由 managed 級設定關掉**）\[1]。
* 組織可用 `allowManagedHooksOnly: true` 擋下使用者 / 專案 / plugin 層 hook（plugin 透過 `enabledPlugins` 強制啟用者除外）。
* 保持 Claude Code >= v2.0.65（CVE-2026-21852 修復版）+ >= v2.1.139（hook 改用 `systemMessage` / `terminalSequence` 而非直接寫 `/dev/tty`）\[1, 2]。

## 10. 工具對照

各家工具對 hook 機制的支援差異極大（截至 2026-06）：

| 概念           | Anthropic Claude（主範本）                                                                                  | OpenAI Codex                                       | Google Antigravity   | GitHub Copilot CLI     | Cursor（短提） |
| ------------ | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------- | -------------------- | ---------------------- | ---------- |
| 工具前鉤子        | PreToolUse，於 `settings.json`                                                                           | Codex hooks（`config.toml`）                         | Antigravity workflow | Copilot hooks（preview） | （無一等公民機制）  |
| 工具後鉤子        | PostToolUse，於 `settings.json`                                                                          | 設定式                                                | 設定式                  | 設定式                    | 不適用        |
| Session 起訖鉤子 | SessionStart / SessionEnd / Stop                                                                       | 設定式                                                | 設定式                  | 設定式                    | 不適用        |
| 事件數          | 30 個生命週期事件 \[1]                                                                                        | 設定式                                                | 設定式                  | 設定式                    | 不適用        |
| Handler 類型   | command / http / mcp\_tool / prompt / agent                                                            | command 為主                                         | command 為主           | command 為主             | 不適用        |
| matcher 語法   | 精確 / `\|` / regex 三模式                                                                                  | 設定式                                                | 設定式                  | 設定式                    | 不適用        |
| 攔截機制         | `exit 2` + `permissionDecision: deny`                                                                  | 設定式                                                | 設定式                  | 設定式                    | 不適用        |
| 注入 context   | `additionalContext`（10k 上限）                                                                            | 不適用                                                | 不適用                  | 不適用                    | 不適用        |
| 設定位置         | `~/.claude/settings.json`、`.claude/settings.json`、plugin `hooks/hooks.json`、Skill/Subagent frontmatter | `~/.codex/config.toml`、`<repo>/.codex/config.toml` | 設定式                  | 設定式                    | 不適用        |
| 確定性保證        | 硬保證（`exit 2`）                                                                                          | 軟保證                                                | 軟保證                  | 軟保證                    | 不適用        |

<Note>
  **命名澄清與邊界**

  * **Claude Code 的 hook 系統截至 2026-06 是 30 個事件的完整生命週期鉤子**（\[1]）；其他 CLI 工具的「hook」多指 command hook 一類，事件數遠少。
  * **OpenAI Codex、Antigravity、GitHub Copilot CLI** 的 hook 機制演進快，使用前以各家當前官方文件為準。
  * **Cursor** 截至 2026-06 未在 CLI / IDE 提供一等公民的 hook 機制；本 Playbook 短提一欄。
  * 本庫的 `validate_kb --hook`（Stop 模式）以本表主範本（Claude Code）的語意設計。
</Note>

## 11. 自我檢核動手做

<Note>
  **30 分鐘練習**

  1. **PostToolUse 自動格式化**：照本庫的 `PostToolUse` 範本，在 `.claude/settings.json` 接上 `normalize_punctuation.py`，編輯任一 `.md` 觀察自動正規化。
  2. **PreToolUse 攔截危險 Bash**：寫 `block-rm.sh` 與對應設定；嘗試讓 Claude 跑 `rm -rf /tmp/xxx` 觀察 `permissionDecision: deny` 與給 Claude 的 reason。
  3. **Stop 收尾驗證**：接上 `validate_kb.py --hook`，故意製造一個 frontmatter 缺 `updated` 欄的單元，觀察 Stop hook 擋下並回報。
  4. **供應鏈掃描**：在 `~/.claude/` 與 `.claude/` 跑本節的 `rg` 掃描，確認沒有可疑外連、沒有隱藏 Unicode。
  5. **SessionStart 注入 env**：寫一個 SessionStart hook 透過 `CLAUDE_ENV_FILE` 設 `MY_FLAG=1`，在新 session 跑 `echo $MY_FLAG` 驗證生效。
</Note>

## 12. 常見誤區

<Warning>
  **反模式清單**

  * **PostToolUse hook 未設 timeout**：每次存檔觸發一個長任務，多次快速存檔後任務堆積、消耗資源。每個 hook 都該有 `timeout` 與合理上限。
  * **把昂貴的全量型別檢查綁在每次 Write/Edit，沒加 `--incremental` 旗標**：拖慢迭代節奏。tsc / mypy / ruff 都有增量模式，務必用。
  * **從公開 repo 複製 hook 配置直接貼上使用，未先掃描 `command` 內容**：引入供應鏈風險。貼之前先 `rg` 掃、讀每個 `command` 的 shell code。
  * **`exit 1` 期待阻擋**：Unix 直覺是 `exit 1` 是錯誤、該擋下。Claude Code hook 系統不是：`exit 1` 是非阻斷錯誤，只有 `exit 2` 對特定事件才阻擋。要擋下用 `exit 2` 或回 JSON `permissionDecision: deny`。
  * **PreToolUse 用 `exit 2` 攔截但沒給 Claude 原因**：`exit 2` 把 stderr 餵回 Claude，但內容只是純文字。對 `PreToolUse` 來說，回 JSON 帶 `permissionDecision: deny` + `permissionDecisionReason` 更結構化，模型也更容易理解與遵守 \[1]。
  * **把「希望模型做」寫成 hook**：hook 跑在 shell 層、不在模型推理鏈內。希望模型「記得」的事寫在 `CLAUDE.md` 或 rules；希望「系統保證執行」的事才走 hook。混淆這層會讓 hook 跑一堆 context 維護邏輯、浪費 shell 資源。
  * **`matcher` 寫 regex 卻用 `\|` 混搭**：`\|` 只在精確清單語法生效。混搭其他字元（如 `Bash|Edit\.ts`）會被當 regex 解析、可能不是你要的行為 \[1]。
</Warning>

## 自我檢核

<Check>
  **通過本單元的標準**

  1. 你能說出 hook 與 `CLAUDE.md` / rules 的根本差別嗎？（shell 確定性 vs. context 軟約束）
  2. 你能在 5 分鐘內寫出一個 `PreToolUse` 攔截 `git commit` 含 `.env` 的 hook 嗎？
  3. 你能說出 `exit 0` / `exit 2` / 其他 exit code 在不同事件的語義嗎？
  4. 你能說出 hook 作為可執行程式碼的三條供應鏈防護 SOP 嗎？
  5. 在你目前的工作流中，有哪一件「每次應該做但不一定記得做」的事，可以改用 PostToolUse 或 Stop hook 來保證？說出觸發事件、要跑的指令、以及選 PostToolUse 還是 Stop 的理由。
</Check>

## 來源與延伸閱讀

事實主張依官方文件，快變動項標註截至 2026-05。

<div className="references">
  * \[1] Anthropic, "Hooks," code.claude.com, 2026. \[Online]. Available: [https://code.claude.com/docs/en/hooks](https://code.claude.com/docs/en/hooks) （截至 2026-06；含 30 個事件、五種 handler 類型、matcher 三模式解析、`exit 2` 阻擋語義、`additionalContext` 注入、`v2.1.139+` 改用 `terminalSequence`）

  * \[2] Anthropic, "Claude Code release notes," code.claude.com, 2026. \[Online]. Available: [https://code.claude.com/docs/en/changelog](https://code.claude.com/docs/en/changelog) （截至 2026-06；CVE-2025-59536 與 CVE-2026-21852 修復版本）

  * \[3] Anthropic, "Extend Claude with skills," code.claude.com, 2026. \[Online]. Available: [https://code.claude.com/docs/en/skills](https://code.claude.com/docs/en/skills) （截至 2026-06；Skill frontmatter 內 `hooks` 設定）

  * \[4] Anthropic, "Create custom subagents," code.claude.com, 2026. \[Online]. Available: [https://code.claude.com/docs/en/subagents](https://code.claude.com/docs/en/subagents) （截至 2026-06；Subagent frontmatter 內 `hooks` 與 `Stop→SubagentStop` 自動轉換）
</div>

* 設定層級模型見 [02-1 設定的層級模型](/code-agent/configuration/config-layer-model)。
* `CLAUDE.md` 軟約束與 hook 硬保證的分工見 [04-1 CLAUDE.md 與記憶檔](/code-agent/customization/claude-md-memory)。
* rules path-scoped 機制見 [04-2 rules](/code-agent/customization/rules)。
* Skill frontmatter 內 hook 見 [04-4 Skill](/code-agent/customization/skills)。
* Subagent 生命週期 hook 見 [04-5 Subagent](/code-agent/customization/subagents)。
* 供應鏈風險見 [03-3 安全、隱私與供應鏈風險](/code-agent/judgment/security-privacy-supply-chain)。
