← 所有文章

Claude Code Hooks 教學:從零打造 5 個生產級 Hook

From the guide: Claude Code Comprehensive Guide

Claude Code 大約有 95% 的時候能執行正確的操作。剩下的 5% 則包括強制推送到 main、跳過格式化工具,以及提交未通過 lint 的程式碼。Hook 透過在 Claude 工作流程的 17 個生命週期節點加入確定性閘門,徹底消除這 5% 的風險。它們每次都會觸發,毫無例外,不受提示措辭或模型行為影響。

摘要: Hook 是由 Claude Code 生命週期事件觸發的 shell 命令。PreToolUse hook 可檢查並阻止操作(exit code 2 = 阻止,exit 0 = 允許)。PostToolUse hook 則在操作完成後進行驗證與格式化。在 .claude/settings.json 中以 matcher 正規表達式和巢狀 hooks 陣列進行設定。本教學將建立五個生產級 hook:自動格式化、安全閘門、測試執行器、通知提醒,以及提交前品質檢查。

重點摘要

  • 個人開發者: 從自動格式化(Hook 1)和安全閘門(Hook 2)開始。這兩個 hook 能以零維護成本防止最常見的 Claude Code 錯誤。
  • 團隊負責人: 將 hook 提交至您的儲存庫中的 .claude/settings.json。每位團隊成員都能自動獲得相同的安全閘門與品質檢查。
  • 安全工程師: Exit code 2 會阻止操作。Exit code 1 僅記錄警告。每個 PreToolUse 安全 hook 都必須使用 exit 2,否則無法提供任何強制力。

什麼是 Hook?

Hook 是在 Claude Code 工作階段中特定生命週期事件觸發時執行的 shell 命令。它們以純腳本的形式在 LLM 之外執行,由 Claude 的操作觸發,而非由模型解讀的提示。

四大類別涵蓋最常見的使用情境(Claude Code 共支援 17 種事件類型):

  • 工作階段事件: SessionStartStop 在工作階段開始或結束時觸發。用於初始設定、清理和通知。
  • 工具事件: PreToolUsePostToolUse 在 Claude 使用工具(寫入檔案、執行 bash 命令或搜尋程式碼)之前和之後觸發。這是最強大的 hook,因為它們能檢查並阻止特定操作。
  • 通知事件: Notification 在 Claude 產生通知時觸發。適用於將警報轉發至 Slack、桌面通知或日誌系統。
  • 子代理事件: SubagentStop 在子代理(透過 Agent 工具產生)完成時觸發。Hook 同樣會對子代理的操作觸發,因此您的安全閘門會遞迴套用。

Exit code 的語意至關重要。 Exit 0 代表成功(繼續執行)。Exit 2 代表阻止操作。Exit 1 代表非阻斷性的 hook 錯誤,操作仍會繼續。每個安全關鍵的 hook 都必須使用 exit 2 才能真正執行閘門功能。


Hook 設定基礎

Hook 存放在您的設定檔中:

  • 專案層級: 儲存庫根目錄的 .claude/settings.json(與團隊共享)
  • 使用者層級: ~/.claude/settings.json(您的個人 hook,全域套用)

JSON 結構:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/your/script.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/another-script.sh"
          }
        ]
      }
    ]
  }
}

每個項目都有一個 matcher(正規表達式,匹配工具名稱如 BashWriteEditReadGlobGrepAgent)和一個 hooks hook 定義陣列。每個 hook 指定 type(shell 命令用 "command")和要執行的 commandWrite|Edit 這個 matcher 會同時匹配兩種工具類型。

您也可以在 Claude Code 工作階段中使用 /hooks 斜線命令互動式管理 hook。

當 hook 觸發時,Claude Code 透過環境變數(檔案操作使用 $FILE_PATH)和 stdin(包含工具名稱、參數和工作階段中繼資料的 JSON 物件)傳遞上下文。您的腳本讀取這些資訊來做出決策。


5 個實用 Hook

以下每個 hook 都解決了我使用 Claude Code 作為主要開發工具時遇到的實際問題。所有範例都使用正確的巢狀 hook 結構。

1. 檔案編輯時自動格式化

Claude 寫出的程式碼功能正確,但偶爾會違反專案的格式規則。與其要求 Claude 重新格式化,不如在每次檔案寫入後自動執行格式化工具。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'if [[ \"$FILE_PATH\" == *.py ]]; then black --quiet \"$FILE_PATH\" 2>/dev/null; elif [[ \"$FILE_PATH\" == *.js ]] || [[ \"$FILE_PATH\" == *.ts ]]; then npx prettier --write \"$FILE_PATH\" 2>/dev/null; fi'"
          }
        ]
      }
    ]
  }
}

Claude Code 會將 $FILE_PATH 設定為被修改的檔案。Hook 檢查副檔名並執行對應的格式化工具。Python 檔案使用 black,JavaScript 和 TypeScript 檔案使用 prettier2>/dev/null 會抑制冗餘輸出,讓您只看到實際錯誤。

對於較大的專案,建議將行內命令移至獨立腳本以提升可讀性。

2. 危險命令的安全閘門

Bash 工具的 PreToolUse hook 會檢查 Claude 即將執行的命令,如果命令匹配危險模式則予以阻止。我寫這個 hook 的起因是 Claude 在一次重構工作階段中強制推送到了 main。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r \".tool_input.command\"); if echo \"$CMD\" | grep -qE \"rm\\s+-rf\\s+/|git\\s+push\\s+(-f|--force)\\s+(origin\\s+)?main|git\\s+reset\\s+--hard|DROP\\s+TABLE|:(){ :|:& };:\"; then echo \"BLOCKED: Dangerous command detected: $CMD\" >&2; exit 2; fi'"
          }
        ]
      }
    ]
  }
}

當此 hook 以 exit code 2 退出時,Claude Code 會取消待執行的命令。錯誤訊息會同時顯示在您的終端機和 Claude 的上下文中,讓模型理解操作失敗的原因並建議更安全的替代方案。

被阻止的模式: - rm -rf /(從根目錄遞迴刪除) - git push --force maingit push -f main(強制推送到 main 分支) - git reset --hard(銷毀未提交的工作) - DROP TABLE(意外的資料庫刪除) - Fork 炸彈

請根據您的環境自訂此清單。生產資料庫需要破壞性 SQL 模式的防護。基於 CLI 的部署則需要部署命令的防護。

3. 變更後自動執行測試

當 Claude 編輯 Python 檔案時,自動執行相關測試。這能立即捕捉回歸問題,而非在三個檔案編輯之後才發現。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'if [[ \"$FILE_PATH\" == *.py ]] && [[ \"$FILE_PATH\" != *test_* ]]; then TEST_FILE=\"tests/test_$(basename \"$FILE_PATH\")\"; if [[ -f \"$TEST_FILE\" ]]; then python -m pytest \"$TEST_FILE\" -x --tb=short 2>&1 | tail -20; fi; fi'"
          }
        ]
      }
    ]
  }
}

此 hook 檢查被編輯的檔案是否為 Python 原始碼檔案(而非測試檔案本身),使用 test_ 前綴命名慣例尋找對應的測試檔案,找到後便執行。-x 旗標會在第一個失敗時停止,tail -20 保持輸出簡潔。

注意: 此 hook 假設使用扁平的 tests/ 目錄結構和 test_ 前綴命名。對於巢狀測試目錄或不同命名慣例,請調整路徑建構邏輯。

此 hook 在重構工作階段中特別有價值,因為 Claude 會觸及多個檔案。即時的回饋迴圈能防止「修好一個東西,壞掉三個」的連鎖反應——這種情況通常發生在測試只在最後才執行的時候。

4. 工作階段結束時發送通知

長時間執行的 Claude Code 工作階段可能耗時數分鐘。與其盯著終端機,不如在工作階段結束時收到通知。

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

這個 macOS 範例使用 osascript 觸發原生通知。Linux 系統請將 osascript 那行替換為 notify-send "Claude Code" "Session ended"。若要發送 Slack 通知,請使用 webhook:

curl -s -X POST "$SLACK_WEBHOOK_URL" \
  -H 'Content-type: application/json' \
  -d '{"text": "Claude Code session ended"}'

我在使用 & <task>(Claude Code 的背景模式)啟動的背景任務中使用 Slack 版本。桌面通知則用於互動式工作階段。

5. 提交前品質檢查

在 Claude 執行 git commit 之前,驗證程式碼是否通過 linting。這能捕捉到僅靠格式化無法發現的問題:未使用的匯入、未定義的變數、型別錯誤。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -c 'INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r \".tool_input.command\"); if echo \"$CMD\" | grep -qE \"^git\\s+commit\"; then if ! LINT_OUTPUT=$(ruff check . --select E,F,W 2>&1); then echo \"LINT FAILED -- fix before committing:\" >&2; echo \"$LINT_OUTPUT\" >&2; exit 2; fi; fi'"
          }
        ]
      }
    ]
  }
}

此 hook 僅在 Bash 命令以 git commit 開頭時觸發。它執行 ruff(快速的 Python linter)並啟用錯誤、pyflakes 和警告規則。如果存在任何問題,提交會被阻止(exit 2),Claude 會看到 lint 輸出,通常會自行修復問題並重試。

您可以疊加多個品質檢查:mypy 做型別檢查、bandit 做安全掃描,或專案自訂的驗證腳本。Bash 命令的 PreToolUse hook 為您提供了一個可程式化的閘門,在任何 shell 操作之前生效。


Hook 除錯技巧

Hook 靜默失敗的頻率比您預期的更高。以下是我用來除錯的五個技巧:

  1. 先獨立測試腳本。 手動將範例 JSON 管道傳入腳本:echo '{"tool_input":{"command":"git commit -m test"}}' | bash your-hook.sh。如果在 Claude Code 外部就失敗了,在內部也一樣會失敗。
  2. 使用 stderr 輸出除錯資訊。 Hook 寫入 stderr 的任何內容都會出現在 Claude 的上下文中。開發時寫入 echo "DEBUG: matched $CMD" >&2,確認 hook 穩定後再移除。
  3. 留意 jq 失敗。 如果 JSON 路徑錯誤,jq 會靜默回傳 null,導致條件判斷無法匹配。請使用實際的工具輸入測試您的 jq 表達式。
  4. 驗證 exit code。 Exit 2 阻止操作。Exit 1 僅發出警告。一個不小心使用 exit 1 的 PreToolUse hook 在看似正常運作的同時,實際上完全沒有強制力。預設使用寬鬆模式(exit 0),只對特定被阻止的模式使用 exit 2
  5. 保持 hook 快速。 Hook 是同步執行的。一個耗時 5 秒的 hook 會為每次匹配的工具使用增加 5 秒延遲。我將所有 hook 控制在 2 秒以內,理想情況下低於 500 毫秒。

下一步

這五個 hook 涵蓋了基本面:格式化、安全、測試、通知和品質閘門。一旦您熟悉了這些模式,就可以建立用於上下文注入(在工作階段開始時新增專案特定指示)、遞迴防護(防止無限子代理迴圈)和工作流程編排(串連多步驟流程)的 hook。

如需了解 hook 架構、全部 17 個生命週期事件及進階模式,請參閱我的完整指南中的 hook 章節:Claude Code 指南:Hook 如何運作?

我也在 Claude Code Hooks:我的 95 個 Hook 各自存在的理由 中撰寫了每個 hook 背後的故事,涵蓋了促使我建立它們的各個事件。


常見問題

Hook 能阻止 Claude Code 執行命令嗎?

可以。PreToolUse hook 透過以 exit code 2 退出來阻止任何工具操作。Claude Code 會取消待執行的操作並將 hook 的 stderr 輸出顯示給模型。Exit 1 是非阻斷性的 hook 錯誤,操作仍會繼續。這個區別至關重要:每個安全 hook 都必須使用 exit 2,而非 exit 1。Claude 會看到拒絕原因並建議更安全的替代方案。

Hook 設定檔應該放在哪裡?

Hook 設定放在 .claude/settings.json 用於專案層級 hook(提交至儲存庫,與團隊共享),或放在 ~/.claude/settings.json 用於使用者層級 hook(個人設定,套用至所有專案)。當兩者同時存在時,專案層級 hook 優先。我建議使用絕對路徑指定腳本檔案,以避免工作目錄問題。

Hook 能與子代理搭配運作嗎?

可以。Hook 同樣會對子代理的操作觸發。如果 Claude 透過 Agent 工具產生子代理,您的 PreToolUse 和 PostToolUse hook 會對子代理使用的每個工具執行。若沒有這個行為,子代理就可能繞過您的安全閘門。SubagentStop 事件讓您在子代理完成任務時執行清理或驗證。

相關文章

How to Set Up Claude Code CLI: 5-Minute Quickstart

Install Claude Code, configure your first project, and run your first agentic coding session in under 5 minutes. Covers …

13 分鐘閱讀

Codex CLI vs Claude Code in 2026: Architecture Deep Dive

Kernel-level sandboxing vs application-layer hooks, AGENTS.md vs CLAUDE.md, cloud tasks vs subagents. A technical compar…

13 分鐘閱讀

Claude Code Hooks: Why Each of My 95 Hooks Exists

I built 95 hooks for Claude Code. Each one exists because something went wrong. Here are the origin stories and the arch…

7 分鐘閱讀