Claude Code Hooks 實戰:讓 AI 工作流自動化的最後一塊拼圖
這是 Claude Code 系列的第六篇。如果你還沒看過前幾篇: 📖 Claude Code 權限控管:用 settings.local.json 保護你的專案 📖 Claude Code 自訂 Slash Commands:打造你的專屬 AI 工作流
🎯 前言
在前幾篇文章中,我們用 settings.local.json 設定了權限控管,用 Custom Slash Commands 定義了工作流程。但有一件事還是做不到:
在 Claude 執行特定動作的「當下」自動觸發一段腳本。
舉個例子:
- Claude 每次編輯完檔案,你想自動跑
prettier格式化 - Claude 要刪除檔案時,你想先檢查是不是重要檔案再決定放不放行
- Claude 結束回答時,你想自動跑測試確認沒有改壞東西
這些需求,settings.local.json 做不到(它只管「能不能用」),Slash Commands 也做不到(它只是 prompt)。
Hooks 就是填補這個空缺的機制。
它是 Claude Code 的事件系統——在特定時間點觸發你寫的腳本,而且是確定性的:不像 prompt 會被 Claude 自由詮釋,hook 腳本每次都會照樣執行。
📂 Hooks 的基本概念
和權限設定的差異
先釐清 Hooks 和 settings.local.json 的權限設定有什麼不同:
| 面向 | 權限設定(permissions) | Hooks |
|---|---|---|
| 功能 | 控制「能不能用」某個工具 | 在工具執行前後「做一件事」 |
| 行為 | 允許 / 拒絕 / 詢問 | 執行腳本、發 HTTP、跑 LLM 判斷 |
| 彈性 | 只能 allow / deny | 可以修改輸入、注入上下文、阻擋執行 |
| 時機 | 工具被呼叫時 | 26 種不同事件時間點 |
簡單說:權限設定是「門禁」,Hooks 是「監控攝影機 + 自動化閘門」。
四種 Hook 類型
Hooks 不只能跑 shell 腳本,共有四種類型:
| 類型 | 說明 | 適用場景 |
|---|---|---|
command | 執行 shell 指令 | 跑 linter、格式化、檢查檔案 |
http | POST 事件 JSON 到指定 URL | 發通知到 Slack、寫 log 到外部服務 |
prompt | 單輪 LLM 判斷(回傳 ok/reason) | 用 AI 檢查 commit message 品質 |
agent | 多輪 subagent,可使用工具 | 自動跑測試、驗證任務完成度 |
大多數情況用 command 就夠了。prompt 和 agent 是進階用法,適合需要 AI 判斷的場景。
⚙️ 設定語法
Hooks 寫在 settings.json 中,和權限設定放同一個檔案:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python check_command.py"
}
]
}
]
}
}
結構是:事件名稱 → 匹配規則 → 要執行的 hook 列表。
設定檔位置(和權限設定相同)
| 位置 | 影響範圍 |
|---|---|
~/.claude/settings.json | 全域(所有專案) |
.claude/settings.json | 單一專案(提交到 git) |
.claude/settings.local.json | 單一專案(不提交 git) |
matcher:決定什麼時候觸發
matcher 是正規表達式,用來過濾工具名稱:
"matcher": "Bash" // 只匹配 Bash 工具
"matcher": "Edit|Write" // 匹配 Edit 或 Write
"matcher": "mcp__.*" // 所有 MCP 工具
"matcher": "" // 匹配所有(或省略 matcher)
如果需要更精細的過濾(例如只匹配 git 開頭的 Bash 指令),可以用 if 欄位:
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo 'git command detected'",
"if": "Bash(git *)"
}
]
}
if 使用的是和權限設定相同的語法(工具名(參數模式)),比 matcher 的正規表達式更直覺。
🔑 關鍵事件類型
Hooks 支援 26 種事件,但日常最常用的是這幾個:
PreToolUse(工具執行前)
最重要的 hook。 在 Claude 呼叫任何工具之前觸發,你可以:
- 檢查並阻擋危險操作(exit code 2)
- 修改工具的輸入參數
- 注入額外的上下文給 Claude
- 自動允許特定操作(跳過權限確認)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python scripts/check_dangerous_commands.py"
}
]
}
]
}
}
PostToolUse(工具執行後)
工具成功執行之後觸發。注意:此時動作已經完成,無法復原,但你可以:
- 對修改過的檔案跑格式化
- 記錄操作日誌
- 阻擋 Claude 繼續使用這個結果(
decision: "block")
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npx prettier --write \"$CHANGED_FILE\""
}
]
}
]
}
}
Stop(Claude 結束回答時)
Claude 完成回答、準備停下來時觸發。你可以:
- 驗證任務是否真的完成
- 自動跑測試
- 阻擋停止,讓 Claude 繼續工作
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "npm test 2>&1 | tail -5"
}
]
}
]
}
}
其他實用事件
| 事件 | 用途 |
|---|---|
SessionStart | Session 開始時注入提醒(例如 compact 後重新注入規則) |
Notification | Claude 需要你注意時發桌面通知 |
UserPromptSubmit | 用戶送出 prompt 前做預處理 |
FileChanged | 監控檔案變更(例如 .env 被修改時重載環境變數) |
📊 Exit Code 行為
Hook 腳本的 exit code 決定了後續行為:
| Exit Code | 效果 |
|---|---|
| 0 | 成功,動作繼續。stdout 會被解析為 JSON 輸出 |
| 2 | 阻擋。動作被取消,stderr 會回饋給 Claude |
| 其他 | 非阻擋錯誤。動作繼續,stderr 只在 verbose 模式顯示 |
這個設計很聰明:只有 exit 2 會阻擋操作。腳本自身出錯(exit 1)不會影響 Claude 的正常運作,避免你的 hook 寫壞了導致整個 Claude Code 卡住。
🛡️ 實戰範例
範例 1:阻擋危險的 Bash 指令
最基本的安全防護——在 Claude 執行 Bash 前檢查是否包含危險指令:
scripts/block_dangerous.sh:
#!/bin/bash
# 從 stdin 讀取事件 JSON
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
# 定義危險模式
DANGEROUS_PATTERNS=(
"rm -rf"
"drop table"
"DROP TABLE"
"truncate"
"TRUNCATE"
"> /dev/null"
"format"
"mkfs"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qi "$pattern"; then
echo "⚠️ 偵測到危險指令: $COMMAND" >&2
echo "包含危險模式: $pattern" >&2
exit 2 # 阻擋執行
fi
done
exit 0 # 允許執行
settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash scripts/block_dangerous.sh"
}
]
}
]
}
}
當 Claude 嘗試執行 rm -rf /some/path 時,hook 會阻擋並告訴 Claude 原因,Claude 會換一個更安全的方式。
範例 2:編輯後自動格式化
每次 Claude 修改檔案後,自動跑 Prettier:
scripts/auto_format.sh:
#!/bin/bash
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=""
if [ "$TOOL_NAME" = "Edit" ]; then
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')
elif [ "$TOOL_NAME" = "Write" ]; then
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')
fi
if [ -n "$FILE_PATH" ] && [ -f "$FILE_PATH" ]; then
# 只格式化支援的檔案類型
case "$FILE_PATH" in
*.js|*.ts|*.jsx|*.tsx|*.json|*.css|*.md|*.html)
npx prettier --write "$FILE_PATH" 2>/dev/null
;;
esac
fi
exit 0
settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash scripts/auto_format.sh"
}
]
}
]
}
}
範例 3:保護重要檔案不被修改
防止 Claude 修改 .env、package-lock.json 等不該手動編輯的檔案:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
PROTECTED_FILES=(
".env"
".env.local"
"package-lock.json"
"pnpm-lock.yaml"
"yarn.lock"
)
BASENAME=$(basename "$FILE_PATH")
for protected in "${PROTECTED_FILES[@]}"; do
if [ "$BASENAME" = "$protected" ]; then
echo "⛔ 此檔案受保護,不允許修改: $BASENAME" >&2
echo "如果確實需要修改,請手動操作" >&2
exit 2
fi
done
exit 0
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash scripts/protect_files.sh"
}
]
}
]
}
}
範例 4:桌面通知(Claude 需要你注意時)
Claude 在等你回應或需要權限確認時,發一個系統通知:
Windows(PowerShell):
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "powershell -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code 需要你的注意', 'Claude Code')\"",
"shell": "powershell"
}
]
}
]
}
}
macOS:
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code 需要你的注意\" with title \"Claude Code\"'"
}
]
}
]
}
}
範例 5:用 AI 驗證任務完成度(進階)
這是最強大的用法——用 agent 類型的 hook,在 Claude 停下來時自動派一個 subagent 跑測試:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "檢查最近修改的檔案是否有對應的測試。如果有測試,執行測試並確認通過。如果測試失敗或缺少測試,回傳 {\"ok\": false, \"reason\": \"測試失敗或缺少測試\"}。如果一切正常,回傳 {\"ok\": true}。",
"timeout": 60
}
]
}
]
}
}
如果 subagent 判斷測試沒通過,Claude 會被阻止停止,繼續修復問題。
💡 Hook 腳本接收的資料
每個 hook 腳本會從 stdin 收到一段 JSON,包含事件的完整資訊:
{
"session_id": "abc123",
"cwd": "/path/to/project",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test",
"timeout": 120000
}
}
你可以用 jq 解析需要的欄位:
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
PreToolUse 的 JSON 輸出(進階控制)
如果你的 hook 腳本輸出 JSON 到 stdout(exit 0),可以做更精細的控制:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "此操作不允許",
"updatedInput": {
"command": "修改後的指令"
},
"additionalContext": "注入給 Claude 的額外上下文"
}
}
| 欄位 | 說明 |
|---|---|
permissionDecision | allow(自動允許)、deny(阻擋)、ask(詢問用戶) |
updatedInput | 修改工具的輸入參數 |
additionalContext | 注入額外上下文給 Claude |
⚠️ 注意事項與陷阱
1. Stop hook 的無窮迴圈
如果你的 Stop hook 阻擋了 Claude 停止,Claude 會繼續工作,完成後又觸發 Stop hook……無限循環。
解法:檢查事件中的 stop_hook_active 欄位:
INPUT=$(cat)
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false')
if [ "$STOP_HOOK_ACTIVE" = "true" ]; then
# 已經在 stop hook 中了,不要再阻擋
exit 0
fi
# 正常的檢查邏輯...
2. Shell profile 污染 JSON 輸出
如果你的 ~/.bashrc 或 ~/.zshrc 有 echo 語句,它們會混入 hook 的 stdout,破壞 JSON 解析。
解法:在 shell profile 中加上互動模式判斷:
# ~/.bashrc
if [[ $- == *i* ]]; then
echo "Welcome!" # 只在互動模式才執行
fi
3. PostToolUse 無法復原
PostToolUse 在工具已經執行完之後才觸發。你可以阻擋 Claude 繼續使用結果,但動作本身無法撤銷。
如果要阻擋操作,用 PreToolUse。
4. 多個 hook 的衝突解決
當同一個事件有多個 hook 同時觸發,Claude Code 取最嚴格的結果。只要有一個 hook 回傳 deny,操作就會被取消,不管其他 hook 怎麼說。
5. 預設 timeout
| 類型 | 預設 timeout |
|---|---|
command | 600 秒(10 分鐘) |
http | 30 秒 |
prompt | 30 秒 |
agent | 60 秒 |
如果你的腳本可能跑比較久(例如跑完整測試),記得設定 timeout。
📋 我的實際設定
結合前幾篇文章的設定,我在 AppSystem 專案中的 .claude/settings.local.json:
{
"permissions": {
"allow": [
"Bash(dir:*)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash scripts/block_dangerous.sh",
"statusMessage": "正在檢查指令安全性..."
}
]
},
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash scripts/protect_files.sh",
"statusMessage": "正在檢查檔案保護規則..."
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "powershell -Command \"Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('Claude Code 需要你的注意', 'Claude Code')\"",
"shell": "powershell"
}
]
}
]
}
}
這個設定搭配權限控管一起使用:
- 權限層:只有
dir可以自動執行,其他 Bash 指令都要手動確認 - Hook 層:即使手動確認了,還是會檢查是否包含危險指令
兩層防護,各司其職。
🎉 結語
Hooks 是 Claude Code 自動化的最後一塊拼圖。
| 機制 | 解決什麼問題 |
|---|---|
| CLAUDE.md | Claude 怎麼理解你的專案 |
| settings.local.json | Claude 能用哪些工具 |
| Custom Slash Commands | Claude 怎麼執行工作流程 |
| Hooks | 在 Claude 動作的當下自動做一件事 |
建議的入門順序:
- 先加 Notification hook — 最簡單,立刻有感,Claude 需要你時會通知你
- 再加 PreToolUse 保護 — 阻擋危險指令和受保護檔案
- 最後嘗試 PostToolUse 自動化 — 自動格式化、記錄日誌
不需要一次全加。先從一個你覺得最痛的問題開始,跑幾天看效果,再慢慢擴充。
📎 相關文章: