Claude Code Hooks 實戰:8 個讓 AI 不再闖禍的自動化規則

CLAUDE.md 是建議,Hooks 是法律。8 個可直接複製的自動化規則,讓 Claude Code 不再 git add .、不看 diff 就 commit、重試到死。附完整 settings.json 範例。

Claude Code Hooks 實戰:8 個讓 AI 不再闖禍的自動化規則

你有沒有過這種經驗:讓 Claude Code 幫你改 code,它順手把 .env 也改了。或是它很熱心地 git add . 把整個目錄都 stage 了,包括你的 credentials。又或者它 commit 了一個你根本沒看過 diff 的變更。

你在 CLAUDE.md 裡寫了「不要碰 .env」「不要 git add .」「commit 前先 diff」,但 CLAUDE.md 只是建議 — Claude 讀了,大概 80% 的時間會遵守。剩下 20% 就是你的 debug 時間。

Hooks 不一樣。它們是硬性的自動化規則 — 每當 Claude 要執行命令、編輯檔案或結束任務時,自動觸發。 不是「請你不要」,是「你不能」。

這篇文章分享我實際在用的 8 個 Hook,每個都附上可以直接複製的程式碼。從今天開始,讓你的 Claude Code 自己守規矩。


Hook 是什麼?30 秒懂

Hook 是 Claude Code 在特定生命週期事件觸發的 shell script。你在 ~/.claude/settings.json(全域)或專案的 .claude/settings.json(專案級)裡設定,Claude 就會在對應時機自動執行。

主要的生命週期事件:

事件 觸發時機 常見用途
PreToolUse Claude 要呼叫工具之前 攔截危險命令、改寫指令
PostToolUse 工具執行完畢之後 語法檢查、審計日誌
FileChanged 檔案被修改後 自動 lint
SessionStart Session 開始時 載入環境、設定狀態
Stop Claude 停下來時 自動學習、提醒收尾
UserPromptSubmit 使用者送出 prompt 時 關鍵字偵測、scope 檢查

一個 Hook 腳本的核心邏輯:

  1. 從 stdin 讀取 JSON(包含工具名稱、參數等)
  2. 判斷是否需要攔截
  3. 正常放行 → exit 0(不輸出)
  4. 攔截 → exit 0 + 輸出特定 JSON(告訴 Claude「不准做」)
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "你的攔截理由,Claude 會看到這段話"
  }
}

Hook 1:禁止 git add .(防止全目錄 stage)

問題:Claude 喜歡用 git add .git add -A 一次 stage 所有變更,可能把 .env、credentials、或你不想 commit 的實驗檔案一起加進去。

解法:攔截所有 git add . / git add -A / git add --all,強制逐檔 stage。

#!/usr/bin/env bash
# pre-bash-git-add-guard.sh
set -uo pipefail

INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty' 2>/dev/null)
[[ "$TOOL_NAME" != "Bash" ]] && exit 0

COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
[[ -z "$COMMAND" ]] && exit 0

if echo "$COMMAND" | grep -qE 'git\s+add\s+(\.|--all|-A)\b'; then
    echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"⚠️ git add . / --all / -A 禁止。請逐檔 stage:git add <specific-file>"}}'
    exit 0
fi

exit 0

效果:Claude 會被強制用 git add src/main.py src/utils.py 這種方式,你在 diff 裡能清楚看到每個被 stage 的檔案。


Hook 2:Commit 前必須先看 Diff

問題:Claude 有時候改完 code 直接 git commit,你根本不知道它改了什麼就進 commit history 了。

解法:追蹤 session 內是否執行過 git diff,沒有的話攔截 git commit

#!/bin/bash
# pre-bash-commit-diff-guard.sh
set -uo pipefail

STATE_DIR="$HOME/.claude/hooks-state"
DIFF_LOG="$STATE_DIR/git-diff-seen.log"
mkdir -p "$STATE_DIR" 2>/dev/null || exit 0

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name // ""' 2>/dev/null) || exit 0
[[ "$tool_name" != "Bash" ]] && exit 0

command=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null) || exit 0
[[ -z "$command" ]] && exit 0

# 記錄 diff 執行
if printf '%s' "$command" | grep -Eq 'git\s+diff'; then
  date +%s > "$DIFF_LOG" 2>/dev/null || true
  exit 0
fi

# 攔截 commit(如果沒先看 diff)
if printf '%s' "$command" | grep -Eq 'git\s+commit'; then
  if [[ ! -f "$DIFF_LOG" ]]; then
    reason="📋 git commit 前必須先執行 git diff 確認變更內容。請先跑 git diff --stat 再 commit。"
    jq -cn --arg reason "$reason" \
      '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":$reason}}'
    exit 0
  fi
  # diff 看過了,清除狀態(下次 commit 要重新看)
  rm -f "$DIFF_LOG" 2>/dev/null
fi

exit 0

效果:Claude 會被迫先跑 git diff --stat,你在對話裡能看到變更摘要,確認沒問題再 commit。


Hook 3:危險命令硬攔截

問題git push --forcerm -rfDROP TABLE — 這些命令一旦執行就很難回滾。

解法:偵測危險關鍵字,直接 deny。

#!/bin/bash
# pre-bash-danger-guard.sh
set -uo pipefail

input=$(cat)

# Fast path: 不含危險關鍵字直接放行
case "$input" in
  *'--force'*|*'reset --hard'*|*'clean -f'*|*'rm -rf'*|\
  *'mkfs'*|*'shutdown'*|*'reboot'*|\
  *'DROP '*|*'drop '*|*'DELETE FROM'*|*'delete from'*)
    ;; # 可能危險,繼續檢查
  *)
    exit 0
    ;;
esac

tool_name=$(echo "$input" | jq -r '.tool_name // ""')
[[ "$tool_name" != "Bash" ]] && exit 0

command=$(echo "$input" | jq -r '.tool_input.command // ""')
[[ -z "$command" ]] && exit 0

# Git 危險操作
if echo "$command" | grep -qE 'git\s+push\s+.*--force|git\s+push\s+-f\b'; then
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"🛑 git push --force 禁止。請使用 --force-with-lease 或確認後手動執行。"}}'
  exit 0
fi

if echo "$command" | grep -qE 'git\s+reset\s+--hard'; then
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"🛑 git reset --hard 禁止。請先 git stash 保存工作進度。"}}'
  exit 0
fi

# 系統毀滅指令
if echo "$command" | grep -qE 'rm\s+-rf\s+(/|~|\$HOME)'; then
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"🛑 危險的 rm -rf 指令。目標路徑可能影響系統或家目錄。"}}'
  exit 0
fi

# DB 危險操作
if echo "$command" | grep -qiE 'DROP\s+(DATABASE|TABLE)|DELETE\s+FROM\s+\S+\s*;'; then
  echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"🛑 DB 危險操作。DROP/DELETE 需要手動確認後執行。"}}'
  exit 0
fi

exit 0

要點:開頭的 case 語句是 fast path — 大多數正常指令不含這些關鍵字,直接 exit 0 跳過,幾乎零開銷。


Hook 4:敏感檔案偵測(.env / credentials)

問題:Claude 可能讀取或輸出含有 API key 的檔案內容,這些 key 會出現在你的對話 context 裡。

解法:偵測涉及敏感檔案的命令,發出警告讓 Claude 自動遮蔽。

#!/bin/bash
# pre-bash-secrets-guard.sh(警告版,不阻擋)
set -uo pipefail

input=$(cat)

# Fast path
case "$input" in
  *'.env'*|*'API_KEY'*|*'SECRET'*|*'TOKEN'*|*'PASSWORD'*|\
  *'PRIVATE_KEY'*|*'credentials'*|*'key.json'*|*'.pem'*)
    ;;
  *)
    exit 0
    ;;
esac

tool_name=$(echo "$input" | jq -r '.tool_name // ""')
[[ "$tool_name" != "Bash" ]] && exit 0

command=$(echo "$input" | jq -r '.tool_input.command // ""')
[[ -z "$command" ]] && exit 0

# 檢查是否涉及敏感檔案
if echo "$command" | grep -qE '\.(env|pem|key|credentials|key\.json)(\s|$|")'; then
  echo "⚠️ SECRETS GUARD: 這個命令可能涉及敏感檔案。請在輸出中遮蔽所有 API keys、tokens、passwords。"
fi

exit 0

設計選擇:這個 hook 只警告,不阻擋。因為有時你確實需要 Claude 幫你處理 .env,但它會在輸出中自動用 *** 取代敏感值。如果要硬擋,把 echo 那行改成 deny JSON 即可。


Hook 5:Python 檔案自動 Lint

問題:Claude 改完 Python 檔案後,你得手動跑 linter 檢查格式和錯誤。

解法:用 FileChanged 事件,每次 .py 檔案被修改就自動跑 ruff check

#!/usr/bin/env bash
# file-changed-lint.sh — FileChanged hook
set -euo pipefail

input=$(cat)
file_path=$(echo "$input" | jq -r '.file_path // ""' 2>/dev/null)
[[ -z "$file_path" ]] && exit 0
[[ "$file_path" != *.py ]] && exit 0

if ! command -v ruff &>/dev/null; then
  exit 0
fi

issues=$(ruff check "$file_path" --no-fix 2>/dev/null | head -5)
if [[ -n "$issues" ]]; then
  printf '{"message": "ruff: %s"}' "$(echo "$issues" | tr '\n' ' ' | head -c 200)"
fi

效果:Claude 會立刻看到 lint 錯誤,通常它會自動修復。比你事後在 CI 裡才發現省太多時間了。

擴展:同樣的模式可以套用到任何語言 — TypeScript 用 eslint、Go 用 go vet、Rust 用 clippy。只要改 matcher 和命令。


Hook 6:撞牆停手(同一命令重試 3 次就擋)

問題:Claude 遇到錯誤時會不斷重試同一個命令,有時候重試 10 次都是同樣的結果,白燒 token。

解法:追蹤每個命令的執行次數,5 分鐘內同一命令執行 2 次後,阻擋第 3 次。

#!/bin/bash
# pre-bash-retry-guard.sh
set -uo pipefail

STATE_DIR="$HOME/.claude/hooks-state"
STATE_FILE="$STATE_DIR/bash-retry.log"
mkdir -p "$STATE_DIR" 2>/dev/null || exit 0
touch "$STATE_FILE" 2>/dev/null || exit 0

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name // ""' 2>/dev/null) || exit 0
[[ "$tool_name" != "Bash" ]] && exit 0

command=$(echo "$input" | jq -r '.tool_input.command // ""' 2>/dev/null) || exit 0
[[ -z "$command" ]] && exit 0

# 白名單:唯讀命令不限制重試
case "$command" in
  "git status"*|"git log"*|"git diff"*|"ls"*|"cat"*|\
  "head"*|"tail"*|"grep"*|"which"*|"pwd"*)
    exit 0
    ;;
esac

# 計算命令 hash
cmd_hash=$(printf '%s' "$command" | md5sum | cut -c1-16)
now=$(date +%s)
cutoff=$((now - 300))  # 5 分鐘窗口

# 清理過期記錄 + 計算重試次數
count=0
temp_file=$(mktemp)
while IFS='|' read -r ts hash; do
  [[ -z "$ts" ]] && continue
  if (( ts > cutoff )); then
    echo "$ts|$hash" >> "$temp_file"
    [[ "$hash" == "$cmd_hash" ]] && ((count++))
  fi
done < "$STATE_FILE"

# 記錄本次
echo "$now|$cmd_hash" >> "$temp_file"
mv "$temp_file" "$STATE_FILE"

if (( count >= 2 )); then
  reason="🔄 RETRY GUARD: 這個命令 5 分鐘內已執行 $count 次且未成功。請停下來分析 root cause,不要繼續重試。"
  jq -cn --arg reason "$reason" \
    '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":$reason}}'
  exit 0
fi

exit 0

這個 hook 是省錢神器。 我實測過,一個卡住的 build 錯誤如果沒有這個 hook,Claude 會重試 5-8 次(每次都帶完整 context),大概燒掉 10-20 萬 token。有了它,第 3 次就會停下來告訴你「我卡住了,需要你看一下」。


Hook 7:治理檔案保護

問題:Claude 可能會「好心地」幫你改 CLAUDE.md、settings.json 或其他治理檔案,結果把你的規則覆蓋掉了。

解法:對特定路徑下的檔案,攔截所有 Edit/Write 操作。

#!/bin/bash
# pre-edit-governance-guard.sh
set -uo pipefail

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name // ""')

case "$tool_name" in
  Edit|Write) ;;
  *) exit 0 ;;
esac

file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
[[ -z "$file_path" ]] && exit 0

# 保護的路徑清單
case "$file_path" in
  */.claude/CLAUDE.md|*/.claude/settings.json|*/.claude/settings.local.json)
    reason="🛡️ GOVERNANCE GUARD: 核心治理檔案受保護。請說明要改什麼,由使用者手動修改。"
    ;;
  */.claude/rules/*)
    reason="🛡️ GOVERNANCE GUARD: rules/ 是治理規則目錄。新增或修改請先寫到 ~/Documents/ 讓使用者 review。"
    ;;
  *)
    exit 0
    ;;
esac

jq -cn --arg reason "$reason" \
  '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":$reason}}'
exit 0

為什麼需要這個:CLAUDE.md 和 rules/ 是你對 AI 的「法律」。如果 AI 能自己改自己的法律,那法律就沒有意義了。這個 hook 確保治理檔案的修改權永遠在人類手上。


Hook 8:Token 節省改寫(RTK)

問題:Claude 每次跑 git statusls -lafind . 這些命令,回傳的結果會塞進 context,吃掉大量 token。

解法:用 RTK(Rust Token Killer)自動改寫常見命令,過濾掉 AI 不需要的輸出。

#!/usr/bin/env bash
# rtk-rewrite.sh(簡化版概念)
# 實際版本由 rtk 官方維護,這裡展示核心概念

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name // ""')
[[ "$tool_name" != "Bash" ]] && exit 0

command=$(echo "$input" | jq -r '.tool_input.command // ""')

# 用 rtk rewrite 改寫命令(省 60-90% token)
rewritten=$(echo "$input" | rtk rewrite 2>/dev/null)
if [[ $? -eq 0 && -n "$rewritten" ]]; then
  echo "$rewritten"
  exit 0
fi

exit 0

效果git status 的輸出從幾百行壓縮到關鍵資訊,ls -la 過濾掉 AI 不需要的 metadata。實測每個月省下 60-90% 的 token,相當可觀。


怎麼裝?完整的 settings.json 範例

把上面的腳本存到 ~/.claude/scripts/ 目錄,然後在 ~/.claude/settings.json 加入:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/scripts/pre-bash-git-add-guard.sh",
            "timeout": 3
          },
          {
            "type": "command",
            "command": "bash ~/.claude/scripts/pre-bash-commit-diff-guard.sh",
            "timeout": 3
          },
          {
            "type": "command",
            "command": "bash ~/.claude/scripts/pre-bash-danger-guard.sh",
            "timeout": 5
          },
          {
            "type": "command",
            "command": "bash ~/.claude/scripts/pre-bash-secrets-guard.sh",
            "timeout": 5
          },
          {
            "type": "command",
            "command": "bash ~/.claude/scripts/pre-bash-retry-guard.sh",
            "timeout": 3
          }
        ]
      },
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/scripts/pre-edit-governance-guard.sh",
            "timeout": 3
          }
        ]
      }
    ],
    "FileChanged": [
      {
        "matcher": "*.py",
        "hooks": [
          {
            "type": "command",
            "command": "bash ~/.claude/scripts/file-changed-lint.sh",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

安裝步驟

# 1. 建目錄
mkdir -p ~/.claude/scripts ~/.claude/hooks-state

# 2. 把上面的腳本存進去
# (每個 hook 存成對應的 .sh 檔案)

# 3. 加執行權限
chmod +x ~/.claude/scripts/*.sh

# 4. 編輯 settings.json
# (把上面的 JSON 合併進你的 settings.json)

# 5. 重啟 Claude Code session

設計 Hook 的 4 個原則

1. Fast Path 先行

大多數命令是安全的。用 case 或簡單的 pattern match 在開頭快速放行,只對可疑命令做進一步檢查。Hook 有 timeout(通常 3-5 秒),如果你的 hook 太慢,會直接被跳過。

2. 能 warn 就不要 deny

不是每個 hook 都需要硬攔截。Secrets guard 用警告而不是阻擋,因為有時你確實需要 Claude 處理 .env。太多 deny 會讓 Claude 不斷重試或繞路,反而更浪費 token。

3. 白名單 > 黑名單

retry-guard 用白名單排除唯讀命令(git statuslsgrep),而不是嘗試列舉所有危險命令。因為你不可能列完所有危險操作,但你可以列完所有安全操作。

4. 狀態用檔案,不用環境變數

Hook 是獨立的 shell process,沒有共享記憶體。需要跨次呼叫的狀態(像 commit-diff-guard 的「有沒有看過 diff」),用檔案儲存在 ~/.claude/hooks-state/


結語:從「請你不要」到「你不能」

CLAUDE.md 是給 AI 的建議。Hook 是自動執行的法律。

兩者不是互斥的 — CLAUDE.md 處理模糊的判斷(「優先用更簡單的方案」),Hook 處理明確的紅線(「禁止 git add .」)。好的規則不靠 AI 自律,靠系統設計。

從今天開始,挑一個最困擾你的問題,寫一個 hook。你會發現,比在 CLAUDE.md 裡寫十遍「不要做 X」有效得多。


我是江中喬,專注於 AI Agent Architecture、Memory Governance 與 Cognitive Diversity,持續研究如何打造能夠長期協作、可信任且可治理的 AI 系統。

ссс