---
title: "10 · 怎麼讓操作 100% 執行"
description: "Hooks 不是提醒 Claude Code 記得做事，而是在工具呼叫、提示提交、停止前後設定確定性檢查點。能容忍遺漏寫 CLAUDE.md，不能容忍就用 Hook。"
canonical: "https://xiangyugongzuoliu.com/tag/claude-code"
verifiedAt: "2026-05-08"
---

Aaron有一條工程習慣：凡是“忘一次就會出事”的規則，不要只寫進提示詞裡。提示詞讓 AI 儘量記住，Hooks（鉤子）讓系統在關鍵節點強制檢查。前者是提醒，後者是門禁。——Aaron

> [!NOTE]
> **這一篇用 14 分鐘換什麼**：第 9 篇講 MCP 讓 Claude Code 夠到外部系統。夠得到以後，風險也變大了。這一篇講 Hooks：怎麼在 Claude Code 準備呼叫工具、呼叫之後、提交提示、準備停止等時間點插入自動檢查，讓“必須發生的事”不再只靠 Claude 記得。

## 1. 先從一個危險的小動作開始

你在 `CLAUDE.md` 裡寫了一條規則：

```md
永远不要修改 `.env` 文件。
```

這條規則有用嗎？有用。Claude Code 會讀到，也大機率遵守。

但“大機率”不是“保證”。

想象某一次任務裡，上下文很長、檔案很多、你又讓它“直接修好本地配置”。Claude 準備寫 `.env`。如果只靠 `CLAUDE.md`，它可能會想起規則，也可能在複雜上下文裡漏掉。

而 `.env` 不是普通檔案。改壞一次，可能就是資料庫連線、生產 token、第三方金鑰出問題。

這時你不應該再問“怎麼讓提示詞寫得更嚴厲”。你應該換一個機制。

> [!IMPORTANT]
> **第一性原理**：Hooks 解決的不是“Claude 不理解規則”，而是“某些規則不能靠模型記憶來執行”。

Claude Code 官方 [Hooks guide](https://code.claude.com/docs/en/hooks-guide) 對 Hooks 的定位很清楚：Hooks 會在 Claude Code 生命週期的特定點自動執行，用來 enforce project rules、automate repetitive tasks、integrate with existing tools。也就是說，它是確定性自動化，不是又一段勸 Claude 聽話的提示詞。

## 2. CLAUDE.md、Permissions、Hooks 各管什麼

先把三個概念擺清楚。

| 機制 | 中文理解 | 強度 | 適合什麼 |
|------|------|------|------|
| `CLAUDE.md` | 工作說明書 | 建議 / 習慣 | 風格、流程、偏好、專案背景 |
| Permissions | 許可權閘門 | 允許 / 詢問 / 拒絕 | 工具級訪問控制 |
| Hooks | 自動檢查點 | 到點必觸發 | 格式化、審計、阻止危險動作、補上下文 |

舉一個 TypeScript 專案：

- “優先用現有 service 層，不要新造抽象”適合寫 `CLAUDE.md`。
- “Bash 裡執行 `rm -rf` 要詢問”適合許可權規則。
- “每次改 `.ts` 檔案後跑 formatter”適合 PostToolUse Hook。
- “任何寫 `.env` 的嘗試都攔下來”適合 PreToolUse Hook。

這三者不是互相替代。

`CLAUDE.md` 給方向，Permissions 管門檻，Hooks 做檢查點。

> [!TIP]
> **一句話理解**：能容忍偶爾遺漏的，寫進 `CLAUDE.md`；需要工具級邊界的，用 Permissions；到某個事件點必須自動發生的，用 Hooks。

## 3. Hook 其實是生命週期檢查點

Hook 的本質是：Claude Code 執行到某個時間點，自動把一段 JSON 輸入交給你配置的 handler（處理器）。handler 可以是命令、HTTP 端點、MCP 工具、prompt 或 agent。

先看一個簡化時間線：

```mermaid
flowchart LR
    Prompt["使用者提交提示"]
    Think["Claude 思考"]
    Pre["PreToolUse<br/>工具執行前"]
    Tool["工具呼叫<br/>Edit / Write / Bash / MCP"]
    Post["PostToolUse<br/>工具成功後"]
    Stop["Stop<br/>準備停止"]

    Prompt --> Think --> Pre --> Tool --> Post --> Think --> Stop

    style Pre fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
    style Post fill:#dcfce7,stroke:#22c55e,stroke-width:2px
    style Stop fill:#f3e8ff,stroke:#a855f7,stroke-width:2px
```

Hooks 不是隻在“寫檔案前”能用。官方 Hooks reference 列了很多事件，比如：

- **`UserPromptSubmit`**：使用者提示提交時觸發，適合注入上下文、攔截危險請求。
- **`PreToolUse`**：工具執行前觸發，適合阻止危險命令、改寫工具輸入。
- **`PermissionRequest`**：即將彈許可權請求時觸發，適合自動允許/拒絕某類請求。
- **`PostToolUse`**：工具成功後觸發，適合格式化、審計、補充反饋。
- **`PostToolUseFailure`**：工具失敗後觸發，適合記錄失敗、給 Claude 修復線索。
- **`Stop`**：Claude 準備停下時觸發，適合檢查任務是否真的完成。
- **`SubagentStop`**：子代理準備結束時觸發，適合審查子代理結果。
- **`PreCompact` / `PostCompact`**：上下文壓縮前後觸發，適合歸檔或重新注入關鍵上下文。
- **`Elicitation` / `ElicitationResult`**：MCP server 請求使用者輸入前後觸發，適合審計或自動處理資訊徵詢。

新手不需要一口氣記完。先記三個最常用的：

- `PreToolUse`：動手前攔。
- `PostToolUse`：動手後補。
- `Stop`：收工前查。

**Hook 觸發順序到底是什麼樣**：一次完整工具呼叫的事件鏈是 `UserPromptSubmit → (思考) → PreToolUse → (Permission 檢查) → 工具執行 → PostToolUse / PostToolUseFailure → (思考) → Stop`。**關鍵事實**：① PreToolUse 在 Permission 檢查**之前**觸發——Hook 可以提前攔掉危險操作不彈許可權窗；② Stop 在準備結束本輪之前觸發——可以阻止 Claude 過早收工。所以 Hook 不是"事後審計"，是**主動嵌入決策鏈路**——這是它跟"日誌"的本質區別。

> [!WARNING]
> **新手坑**：不要為了“控制感”給每個事件都掛 Hook。Hook 越多，執行越慢，干擾越多，排查也越難。

## 4. 一個最小 Hook 長什麼樣

Hooks 寫在 settings JSON 裡。結構有三層：

1. 選事件，比如 `PreToolUse`。
2. 選 matcher group，比如只匹配 `Write` 或 `Bash`。
3. 放一個或多個 hook handler。

比如阻止寫 `.env`：

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

指令碼大概這樣：

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

INPUT="$(cat)"
FILE_PATH="$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')"

case "$FILE_PATH" in
  *.env|*/.env|*.env.*)
    echo "Blocked: environment files may contain secrets and must be edited manually." >&2
    exit 2
    ;;
esac

exit 0
```

Claude Code 會把事件 JSON 透過 stdin 傳給 command hook。上面指令碼讀 `tool_input.file_path`，如果目標是環境檔案，就返回 exit code 2。

> [!IMPORTANT]
> **關鍵點**：Hook 不需要說服 Claude。它在工具執行前已經拿到了結構化輸入，可以直接基於檔案路徑、命令、許可權模式做判斷。

## 5. exit code 2 不只是“報錯”

官方 Hooks reference 明確說：exit 0 表示成功；exit 2 表示 blocking error；其他退出碼對多數事件來說是 non-blocking error。

這很重要：

- `exit 0`：成功，動作繼續；stdout 裡的 JSON 可能被解析。
- `exit 2`：阻止；stderr 會反饋給 Claude。
- `exit 1` 或其他：多數事件下只是非阻塞錯誤，動作繼續。

所以不要用傳統直覺寫：

```bash
exit 1
```

如果你想阻止操作，要寫：

```bash
echo "Blocked: do not edit .env" >&2
exit 2
```

並且記住一個細節：**exit 2 時，Claude Code 忽略 stdout 和 stdout 裡的 JSON，只看 stderr 作為反饋。** 如果你要用 JSON 做更細控制，通常是 exit 0 然後在 stdout 輸出 JSON。

例如 PreToolUse 可以返回：

```json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database writes are not allowed."
  }
}
```

> [!TIP]
> **實操判斷**：簡單阻止用 `stderr + exit 2`；需要更細的 allow / deny / ask / 修改輸入，用 `exit 0 + JSON output`。

## 6. 五種 handler：不只有 shell

舊理解裡，Hook 常被理解成“自動跑一個 shell 指令碼”。現在已經不止這樣。

官方 Hooks reference 裡的 handler type 有五類：

- **`command`**：執行 shell 命令，適合格式化、檔案保護、日誌。
- **`http`**：POST 到一個 URL，適合通知、審計、接外部系統。
- **`mcp_tool`**：調已連線 MCP server 的工具，適合複用現有 MCP 能力。
- **`prompt`**：讓 Claude 模型做一次判斷，適合檢查是否真的完成、是否符合複雜規則。
- **`agent`**：啟動帶工具的 verifier agent，適合需要讀檔案、搜尋後再判斷的場景。

這五種放在一條光譜上看：

```text
确定性强 -> 判断力强
command -> http -> mcp_tool -> prompt -> agent
```

例子：

- 改 `.ts` 後跑 Prettier：`command`。
- 每次許可權請求發審計系統：`http`。
- 複用一個內部 policy MCP server：`mcp_tool`。
- Stop 前判斷“任務是否真的完成”：`prompt`。
- Stop 前讓一個 verifier agent 搜尋測試結果、讀 diff 再判斷：`agent`。

> [!CAUTION]
> **不要反過來用重武器**：能用 command 判斷的，就不要用 prompt；能用 prompt 判斷的，不一定要開 agent。判斷越智慧，成本和不確定性也越高。

**為什麼 Anthropic 設計 5 種 handler 而不是隻給 command**：用 shell command 解決一切是最簡單——但有些判斷 shell 寫不出來。"任務真的完成了嗎"需要語義理解，正則做不到——這就是 prompt handler 的位置。"任務完成了但程式碼風格還有 3 處問題"需要讀程式碼 + 跑命令——這就是 agent handler。**5 種遞進的設計哲學是**：讓你按"判斷複雜度"選合適的工具，不強迫所有規則都在 shell 裡寫——避免出現"寫 100 行 shell 模擬模型推理"的反模式。

## 7. Block-at-Write 還是 Block-at-Submit

Hook 很容易被用重。

比如你想保證測試透過。你可以在每次 `Edit` 後立刻跑全量測試嗎？技術上可以。但這會讓 Claude 每改一個檔案就等很久，任務體驗會崩。

更好的方式是區分兩類規則：

- **Block-at-Write**：動手前或剛動手後立即攔，適合改 `.env`、刪庫、寫生產配置。
- **Block-at-Submit**：收工前統一檢查，適合測試、lint、文件完整性、驗收清單。

危險動作要早攔，因為一旦發生就可能造成損失。

質量檢查可以晚一點，因為 Claude 需要先完成一組相關修改，再統一修 lint、測試和格式。

Stop hook 就適合 Block-at-Submit：

```json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "复盘本轮结果。如果还缺测试或必要的验证步骤，就阻止停止：$ARGUMENTS"
          }
        ]
      }
    ]
  }
}
```

> [!IMPORTANT]
> **底層邏輯**：早攔保護不可逆風險，晚查保護交付質量。不要把所有質量規則都塞到每一次檔案寫入前。

## 8. Hook 配置放在哪裡

Hook 遵守 Claude Code settings scope：

- **`~/.claude/settings.json`**：作用於你所有專案，適合個人通知、個人日誌。
- **`.claude/settings.json`**：作用於當前儲存庫所有協作者，適合團隊規範、安全規則。
- **`.claude/settings.local.json`**：只作用於當前儲存庫你自己，適合本機實驗、臨時除錯。
- **plugin**：作用於外掛啟用處，適合外掛隨帶的 Hook。
- **managed settings**：作用於組織級，適合安全和合規強制策略。

官方配置文件裡，scope 優先順序大致是：managed 最高，其次命令列引數、local、project、user。專案級能覆蓋使用者級，managed 不能被普通使用者覆蓋。

你可以用：

```text
/hooks
```

開啟只讀 hooks 選單，檢查當前有哪些 hook、來自哪個來源、匹配什麼事件。官方說明裡 `/hooks` 是 read-only：想改配置，還是要編輯 settings JSON 或讓 Claude 修改檔案。

> [!WARNING]
> **團隊規則別放錯**：團隊必須共同遵守的 Hook 放 `.claude/settings.json`；只對你本機有意義的通知、路徑和實驗，放 `.claude/settings.local.json` 或使用者級。

## 9. 冪等性和效能

Hooks 是程式碼。程式碼就有工程質量。

第一條是冪等性。

冪等性就是：跑一次和跑多次，結果不應該越來越壞。

好的 Hook：

```text
格式化文件
检查文件路径
追加带唯一 id 的审计日志
```

危險 Hook：

```text
每触发一次就插一条数据库记录
每触发一次就覆盖一个全局配置
每触发一次就发一条无法去重的外部通知
```

Claude Code 可能多次編輯同一個檔案，PostToolUse 就可能多次觸發。如果 Hook 沒有冪等性，就會製造重複副作用。

第二條是效能。

PreToolUse 是熱路徑。你在每次工具呼叫前跑一個 10 秒指令碼，Claude Code 體驗會立刻變慢。

減少成本的方法：

- 用 `matcher` 限定工具名。
- 用 `if` 進一步限定命令或檔案型別。
- 小檢查用 command，不要動不動 prompt/agent。
- 長任務考慮 async，但不要用 async 做必須同步攔截的安全規則。

> [!TIP]
> **Hook 設計原則**：越靠前、越高頻的事件，Hook 越要短、準、可預測。

## 10. 這一篇和前幾篇怎麼連起來

到這裡，Claude Code 的控制層可以串成一張圖。

```mermaid
flowchart TB
    Need["你想控制 Claude Code 行為"]
    Know["長期知識和偏好<br/>CLAUDE.md"]
    Reuse["重複任務流程<br/>Skills"]
    Reach["連線外部系統<br/>MCP"]
    People["隔離或協作<br/>SubAgents / Agent Teams"]
    Guard["確定性檢查點<br/>Hooks"]
    Perm["工具訪問邊界<br/>Permissions"]

    Need --> Know
    Need --> Reuse
    Need --> Reach
    Need --> People
    Need --> Guard
    Need --> Perm

    style Guard fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
    style Perm fill:#fee2e2,stroke:#ef4444,stroke-width:2px
```

每一層解決的問題不同：

- **`CLAUDE.md`**：讓 Claude 知道專案規則，關鍵詞是 should。
- **Skills**：複用任務流程，關鍵詞是 reusable workflow。
- **SubAgents**：隔離旁支任務，關鍵詞是 separate context。
- **Agent Teams**：多會話協作，關鍵詞是 shared task list。
- **MCP**：夠到外部系統，關鍵詞是 external tools。
- **Hooks**：到點自動檢查，關鍵詞是 deterministic checkpoint。
- **Permissions**：哪些工具能用，關鍵詞是 access control。

第 11 篇會專門講 Permissions。先記住邊界：Hook 可以在事件點做檢查，Permissions 決定工具訪問規則。兩者經常一起用，但不是一回事。

> [!IMPORTANT]
> **一句話理解**：Hooks 是“到點必查”的檢查點；Permissions 是“能不能用工具”的訪問邊界；`CLAUDE.md` 是“希望你怎麼做”的說明書。

## 11. 本章自檢

試著用自己的話回答：

1. 為什麼"我已經寫進 CLAUDE.md"不能等同於"這件事一定會發生"？對應 §1-§2。
2. exit 2 和 exit 1 在 Hooks 裡有什麼關鍵區別？如果你想阻止寫 `.env`，應該用哪個？對應 §5。
3. **動手題** ⭐：在你專案根新建 `.claude/hooks/protect-secrets.sh`，寫一個 PreToolUse Hook：當 Claude 想 Edit / Write 任何檔名包含 `.env` / `secret` / `credentials` 的檔案時，用 stderr 輸出"危險檔案不允許編輯"+ exit 2 阻止。然後在 `.claude/settings.json` 註冊。**自檢**：手動讓 Claude 試著改 `.env`，看 Hook 有沒有攔下來 + 看 stderr 資訊有沒有進 Claude 上下文。這一道題做完你會知道：CLAUDE.md 提醒 ≠ 真攔得住，Hook 才是確定性。對應 §1 + §4。

> [!TIP]
> **過關標準**：能用一句話說清：Hooks 是 Claude Code 生命週期裡的確定性檢查點，用來自動執行、阻止或反饋那些不能只靠模型記憶完成的規則。

<details id="glossary">
<summary><b>本篇術語速查表</b></summary>

- **Hook**：鉤子，在 Claude Code 生命週期特定點自動執行的處理器。
- **handler**：處理器，Hook 觸發後真正執行的 command、http、prompt 等。
- **matcher**：匹配器，限定 Hook 對哪些工具或事件生效。
- **`PreToolUse`**：工具執行前，工具呼叫前觸發，可阻止危險動作。
- **`PostToolUse`**：工具執行後，工具成功後觸發，常用於格式化和審計。
- **`Stop`**：停止前，Claude 準備結束當前回合時觸發。
- **exit code**：退出碼，指令碼結束時返回給 Claude Code 的狀態數字。
- **stderr**：標準錯誤輸出，exit 2 時反饋給 Claude 的錯誤資訊來源。
- **idempotency**：冪等性，多次執行結果仍然安全穩定。
- **Permissions**：許可權系統，控制 Claude Code 工具訪問邊界的機制。

</details>

## 官方資料

- [Automate workflows with hooks](https://code.claude.com/docs/en/hooks-guide)
- [Hooks reference](https://code.claude.com/docs/en/hooks)

## 接下來去哪

<Cards>
  <Card href="/zh-Hant/docs/claude-code/understanding/11-permissions" title="11 · 許可權怎麼管" description="Hooks 是檢查點，Permissions 是訪問邊界。下一篇拆 Claude Code 怎麼決定某個工具能不能用、要不要問你。" />
  <Card href="/zh-Hant/docs/claude-code/understanding/09-mcp" title="09 · 怎麼連外部服務（上一篇）" description="MCP 讓 Claude Code 夠到外部系統。Hooks 幫你在這些外部操作前後加檢查和審計。" />
  <Card href="/zh-Hant/docs/claude-code/understanding/03-memory" title="03 · 怎麼記住你的習慣" description="分不清 CLAUDE.md 和 Hooks 時，回到記憶篇：長期習慣寫說明，強制檢查寫自動化。" />
</Cards>

如果只記一個判斷：**凡是“漏一次就會出事”的規則，不要只寫成提醒，要放到 Hook 或 Permission 裡。**
