免費

Claude Code Hooks 完整指南:讓 Claude 的每個動作都在你掌控之中

完整解析 Claude Code Hooks 機制,從五種類型到退出碼控制,搭配四個真實配置案例,讓 AI 操作可審計、可攔截、可自動化。


每次 Claude Code 讀檔案、寫程式碼、執行指令,背後都有一套事件系統在運作。Hooks 就是接入這套系統的介面——你可以在任意時機注入自己的邏輯,自動做程式碼檢查、記錄日誌、攔截危險操作,或者觸發任何 shell 指令。

這篇文章把 Hooks 的機制、設定方式和實戰用法完整講一遍。


Hooks 是什麼

Hooks 是設定在 settings.json 裡的 shell 指令,Claude Code 在特定事件發生時自動執行它們。

最直接的類比:git hooks。git 在 commit、push 等操作前後可以觸發腳本,Claude Code Hooks 的思路完全一樣,只是觸發點是 AI 的工具呼叫。

為什麼這很重要?

Claude Code 的能力越強,你越需要確定性的控制層。Hooks 提供的是:
- 不依賴 prompt 的執行保證(Claude 可能忽略指令,但 hook 一定跑)
- 可審計的操作記錄
- 自動化的品質檢查


五種 Hook 類型

類型 觸發時機 典型用途
PreToolUse 工具呼叫前 攔截危險操作、記錄意圖
PostToolUse 工具呼叫後 自動 lint、執行測試
PreCompact 上下文壓縮前 儲存當前狀態快照
Notification Claude 發出通知時 桌面推播、Slack 訊息
Stop Claude 完成回覆時 彙整日誌、觸發後續流程

最常用的是 PreToolUse 和 PostToolUse,圍繞工具呼叫做攔截和後處理。


設定格式

Hooks 寫在 ~/.claude/settings.json(全域)或專案根目錄的 .claude/settings.json(專案級)裡:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npm run lint --silent"
          }
        ]
      }
    ]
  }
}

三個核心欄位:

  • matcher:匹配工具名的正規表示式,決定這個 hook 在哪些工具呼叫後觸發。"Write|Edit" 表示寫檔案或編輯檔案時觸發。留空或 ".*" 匹配所有工具。
  • type:目前只有 "command"。
  • command:任意 shell 指令。

工具名稱參考

寫 matcher 時需要知道工具叫什麼名字:

工具名稱 對應操作
Write 寫入新檔案
Edit 編輯檔案
Bash 執行 shell 指令
Read 讀取檔案
Glob 檔案搜尋
Grep 內容搜尋
TodoWrite 更新任務清單

Hook 的執行環境

執行時透過 stdin 傳入 JSON:

{
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.rb",
    "content": "..."
  },
  "tool_response": "..."
}

PreToolUse 拿到 tool_input(呼叫前的入參),PostToolUse 同時拿到 tool_response(工具回傳結果)。

可以用這些資料寫有判斷邏輯的 hook:

#!/bin/bash
input=$(cat)
file=$(echo "$input" | jq -r '.tool_input.file_path')

# 只對 .rb 檔案執行 rubocop
if [[ "$file" == *.rb ]]; then
  rubocop "$file" --autocorrect-all --no-color
fi

退出碼的含義

Hook 的退出碼控制 Claude Code 的後續行為:

退出碼 含義
0 成功,繼續執行
2 阻斷:取消當前工具呼叫,將 stderr 輸出回饋給 Claude
其他非零 記錄錯誤,但繼續執行

退出碼 2 是最有用的——它讓你可以在 PreToolUse 裡寫攔截邏輯,阻止 Claude 執行某個操作,並告訴它原因。


實戰:四個真實 Hook 設定

1. 寫檔案後自動格式化

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'file=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path 2>/dev/null); [[ \"$file\" == *.rb ]] && rubocop -A \"$file\" --no-color -q || true'"
          }
        ]
      }
    ]
  }
}

2. 禁止刪除特定目錄

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'cmd=$(echo \"$CLAUDE_TOOL_INPUT\" | jq -r .command); if echo \"$cmd\" | grep -qE \"rm.*/(migrations|seeds)\"; then echo \"禁止刪除 migrations 或 seeds 目錄\" >&2; exit 2; fi'"
          }
        ]
      }
    ]
  }
}

3. 操作日誌

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$(date '+%Y-%m-%d %H:%M:%S') $CLAUDE_TOOL_NAME: $(echo $CLAUDE_TOOL_INPUT | jq -c .)\" >> ~/.claude/audit.log"
          }
        ]
      }
    ]
  }
}

4. 完成後桌面通知

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude 已完成\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

全域 vs 專案級

兩個位置可以放 Hooks:

全域(~/.claude/settings.json):適合所有專案通用的規則,比如日誌、通知。

專案級(.claude/settings.json):適合專案專屬的檢查。專案級 hooks 和全域 hooks 會合併執行,不會互相覆蓋。

團隊專案建議把專案級 settings.json 提交到 git,這樣所有人都跑相同的 hooks。


除錯 Hook

Hook 不生效時的排查步驟:

  1. 確認 matcher 拼寫:工具名區分大小寫,write 不等於 Write
  2. 單獨跑指令:把 hook 裡的 command 複製出來在終端機直接執行
  3. 看 Claude Code 輸出:hooks 的 stderr 會顯示在對話裡
  4. 簡化測試:先用 echo "hook triggered" >> /tmp/hook.log 確認觸發

小結

Hooks 的核心價值是把不確定的 AI 行為和確定的工程規範接在一起。Claude 可能忘記執行測試,但 PostToolUse hook 不會。Claude 可能誤刪檔案,但 PreToolUse 攔截不會放行。

從一個 hook 開始:寫檔案後自動 lint。跑順了再加其他的。