Hooks 60 秒理解
Hooks 是 Claude Code 的「事件驅動自動化」機制——在 Claude 做特定動作前 / 後 / 出錯時自動執行你的 shell 腳本。
為什麼重要:Hooks 是 Claude 的護欄——沒護欄你不敢讓它跑 production。
對應 pocketos 9 秒刪庫事件——那次事件就是因為沒設 hook 攔截 rm -rf。
Hooks vs CLAUDE.md vs Skills vs Slash Commands
| 機制 | 解決什麼 | 觸發 |
|---|---|---|
| CLAUDE.md | 「這個專案的全局規矩」 | 每次 session 自動讀 |
| Skills | 「特定任務的 SOP」 | Claude 自動判斷該用 |
| Slash Commands | 「一鍵啟動的指令」 | 使用者輸入 /xxx |
| Hooks | 「事件發生時的自動腳本」 | 特定事件被觸發 |
| MCP | 「外部工具的橋樑」 | 明確的 tool call |
Hooks 的獨特定位:事件驅動 + 安全護欄——其他 3 個都不能做這件事。
Hooks 三節奏
14 個官方事件分布在三個生命週期:
SessionStart → UserPromptSubmit
↓
[每輪對話]
PreToolUse → ToolExecution → PostToolUse
↓
Stop / StopFailure
↓
SessionEnd
14 個官方事件速查表
| 事件 | 觸發時機 | 可阻擋? | 常見用途 |
|---|---|---|---|
| SessionStart | 新 session 開始 | ❌ | 載入專案狀態、開 audit log |
| SessionEnd | session 結束 | ❌ | 寫日誌、上傳 log |
| UserPromptSubmit | 使用者送出訊息 | ✅ | 掃 PII、攔截危險 prompt |
| PreToolUse | Claude 將呼叫 tool 前 | ✅ | 攔截 rm / sudo / 危險指令 |
| PostToolUse | tool 執行完後 | ❌ | 跑 lint / format / 自動 commit |
| Stop | 任務完成 | ❌ | 發 Slack 通知、寫摘要 |
| StopFailure | 任務失敗 | ❌ | 發警報、寫錯誤 log |
| Notification | 需要使用者輸入 | ❌ | 切到第二螢幕、Slack 通知 |
| PreCompact | 壓縮 context 前 | ✅ | 備份原 context |
| PostCompact | 壓縮後 | ❌ | 寫摘要、驗證 context |
注意:事件清單會隨 Claude Code 更新——以 官方文件 為準。
安裝與第一個 hook(5 分鐘)
settings.json 結構
位置:~/.claude/settings.json(全域)或 <repo>/.claude/settings.json(專案)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"command": "npx prettier --write ${tool.args.file_path}"
}
]
}
}
這個 hook 的意思:每次 Claude 用 Edit 工具改檔後,自動跑 prettier 格式化。
驗證 hook 跑起來
# 在 Claude Code 內
> 改一個 .ts 檔的 import 順序
# 改完後檢查是否自動 prettier
Mason 註:第一次設 hook 建議用「顯眼的副作用」(例如 echo 到 log file)——確認真的被觸發後再加複雜邏輯**。
🎯 25 個 ready-to-copy 觸發點(按任務分類)
這節是這篇文章對 SERP 最有殺傷力的章節——對手都按事件分類,Mason 按「你想做什麼」分類。
🅰️ 類別 A:程式品質與格式化(5 個)
1. Edit 後跑 prettier / black / gofmt
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit",
"command": "npx prettier --write ${tool.args.file_path}"
}]
}
}
Python:black ${tool.args.file_path}
Go:gofmt -w ${tool.args.file_path}
2. Edit 後跑 ESLint 並反饋給 Claude
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit",
"command": "npx eslint ${tool.args.file_path} --fix; echo 'Lint check complete' >&2"
}]
}
}
stderr 輸出會傳回給 Claude——Claude 看到 lint 失敗會自己修。
3. Edit 前確認檔案不在保護清單
{
"hooks": {
"PreToolUse": [{
"matcher": "Edit",
"command": "if grep -q '${tool.args.file_path}' .claude/protected-files.txt; then echo 'BLOCKED: protected file' >&2; exit 1; fi"
}]
}
}
.claude/protected-files.txt 列出絕不能改的檔案(資料庫 migration、production config 等)。
4. 寫 test 後自動跑 test suite
{
"hooks": {
"PostToolUse": [{
"matcher": "Write",
"command": "if [[ '${tool.args.file_path}' == *.test.* ]]; then npm test; fi"
}]
}
}
5. 寫 markdown 後跑 vale / textlint
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"command": "if [[ '${tool.args.file_path}' == *.md ]]; then vale ${tool.args.file_path}; fi"
}]
}
}
Mason 站台用 vale 檢查中文標點 + 用詞一致性。
🔴 類別 B:安全與危險指令攔截(5 個)
6. 攔 rm -rf / sudo / pkill
# .claude/hooks/pre-tool-use-block-dangerous.sh
#!/bin/bash
ARGS=$1
if [[ "$ARGS" == *"rm -rf"* ]] || [[ "$ARGS" == *"sudo"* ]] || [[ "$ARGS" == *"pkill"* ]]; then
echo "BLOCKED: dangerous command detected" >&2
exit 1
fi
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"command": "./.claude/hooks/pre-tool-use-block-dangerous.sh '${tool.args.command}'"
}]
}
}
這個 hook 是 pocketos 事件 的直接解藥。
7. 攔對 production 環境的 deploy 指令
# .claude/hooks/pre-tool-use-block-prod-deploy.sh
#!/bin/bash
CMD=$1
if [[ "$CMD" == *"deploy"* ]] && [[ "$CMD" == *"prod"* ]]; then
echo "BLOCKED: production deploy requires manual approval" >&2
exit 1
fi
8. 攔接觸 .env / credentials.json
# .claude/hooks/pre-tool-use-block-secrets.sh
#!/bin/bash
FILE=$1
if [[ "$FILE" == *".env"* ]] || [[ "$FILE" == *"credentials"* ]] || [[ "$FILE" == *"secret"* ]]; then
echo "BLOCKED: secret files cannot be read by Claude" >&2
exit 1
fi
9. 強制 git status clean 才能跑 destructive 指令
#!/bin/bash
if [[ "$1" == *"git reset --hard"* ]] || [[ "$1" == *"git clean -fd"* ]]; then
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "BLOCKED: uncommitted changes detected. Commit first." >&2
exit 1
fi
fi
10. UserPromptSubmit 掃 PII 自動 redact
#!/bin/bash
PROMPT=$1
# 掃信箱 / 手機 / 身分證
REDACTED=$(echo "$PROMPT" | sed 's/[a-zA-Z0-9._%+-]*@[a-zA-Z0-9.-]*\.[a-zA-Z]\{2,\}/[REDACTED_EMAIL]/g')
REDACTED=$(echo "$REDACTED" | sed 's/09[0-9]\{8\}/[REDACTED_PHONE]/g')
REDACTED=$(echo "$REDACTED" | sed 's/[A-Z][12][0-9]\{8\}/[REDACTED_ID]/g')
echo "$REDACTED"
這個 hook 讓員工不會無意中把客戶個資丟給 Claude。
🔄 類別 C:Git 與 CI/CD 整合(5 個)
11. commit 後自動推給 GitHub Actions
{
"hooks": {
"PostToolUse": [{
"matcher": "Bash",
"command": "if [[ '${tool.args.command}' == *'git commit'* ]]; then gh workflow run ci.yml; fi"
}]
}
}
12. Stop 後寫 daily log
#!/bin/bash
# .claude/hooks/stop-write-daily-log.sh
DATE=$(date +%Y-%m-%d)
SUMMARY=$1
echo "[$DATE $(date +%H:%M)] $SUMMARY" >> ~/.claude/logs/daily-$DATE.log
13. PreToolUse 攔 git push --force 到 main
#!/bin/bash
CMD=$1
if [[ "$CMD" == *"git push --force"* ]] || [[ "$CMD" == *"git push -f"* ]]; then
BRANCH=$(git branch --show-current)
if [[ "$BRANCH" == "main" ]] || [[ "$BRANCH" == "master" ]]; then
echo "BLOCKED: force push to main/master is forbidden" >&2
exit 1
fi
fi
14. PostToolUse Edit 後自動產 PR description 草稿
#!/bin/bash
# 每次改檔後追加到 .claude/pr-draft.md
CHANGES=$(git diff --stat)
echo "## 改動摘要 ($(date +%H:%M))" >> .claude/pr-draft.md
echo "$CHANGES" >> .claude/pr-draft.md
15. Edit 後自動 update 測試覆蓋率
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit",
"command": "if [[ '${tool.args.file_path}' == src/* ]]; then npm run coverage:update; fi"
}]
}
}
📢 類別 D:通知與監控(5 個)
16. Stop 後發 Slack
#!/bin/bash
SUMMARY=$1
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"Claude 完成任務:$SUMMARY\"}" \
$SLACK_WEBHOOK_URL
17. StopFailure 發 Discord + DataDog
#!/bin/bash
ERROR=$1
# Discord 通知
curl -H "Content-Type: application/json" \
-d "{\"content\":\"⚠️ Claude 任務失敗:$ERROR\"}" \
$DISCORD_WEBHOOK
# DataDog 事件
curl -X POST "https://api.datadoghq.com/api/v1/events" \
-H "DD-API-KEY: $DD_API_KEY" \
-d "{\"title\":\"Claude failure\",\"text\":\"$ERROR\",\"alert_type\":\"error\"}"
18. SessionStart 寫 audit log
#!/bin/bash
SESSION_ID=$1
echo "[$(date +%Y-%m-%d_%H:%M)] Session $SESSION_ID started by $USER" >> /var/log/claude/audit.log
19. PreToolUse Bash 指令超過 30 秒前發通知
#!/bin/bash
CMD=$1
# 預估超過 30 秒的指令(可自訂規則)
LONG_PATTERNS=("npm run build" "docker build" "pytest" "terraform apply")
for p in "${LONG_PATTERNS[@]}"; do
if [[ "$CMD" == *"$p"* ]]; then
curl -X POST -d "{\"text\":\"Claude 開始長任務:$p\"}" $SLACK_WEBHOOK
break
fi
done
20. PostToolUse 每次 Edit 寫 file change log
#!/bin/bash
FILE=$1
echo "[$(date)] $USER edited $FILE via Claude" >> ~/.claude/file-changes.log
🤝 類別 E:跨工具 / 跨倉庫一致性(5 個)
21. SessionStart 讀最新 CLAUDE.md
#!/bin/bash
# 從 main 拉最新 CLAUDE.md
git fetch origin main -- CLAUDE.md
git checkout origin/main -- CLAUDE.md
echo "📋 CLAUDE.md 已更新到最新版"
22. PreToolUse Edit 套用團隊 style guide
{
"hooks": {
"PreToolUse": [{
"matcher": "Edit",
"command": "cat .claude/style-guide.md >&2"
}]
}
}
>&2 讓 style guide 直接塞進 Claude 的 context——每次改檔前都會「看到」規則**。
23. PostToolUse 寫文件後同步 Notion
#!/bin/bash
FILE=$1
if [[ "$FILE" == docs/*.md ]]; then
python3 .claude/scripts/sync-to-notion.py "$FILE"
fi
24. SessionEnd 寫摘要到 Linear / Jira
#!/bin/bash
SUMMARY=$1
TICKET=$(git branch --show-current | grep -oE '[A-Z]+-[0-9]+')
if [[ -n "$TICKET" ]]; then
# 寫到對應 ticket
curl -X POST "https://api.linear.app/graphql" \
-H "Authorization: $LINEAR_TOKEN" \
-d "{\"query\":\"mutation { commentCreate(input: {issueId: \\\"$TICKET\\\", body: \\\"Claude session 摘要:$SUMMARY\\\"}) { success } }\"}"
fi
25. PreCompact 備份原 context
#!/bin/bash
SESSION_ID=$1
mkdir -p ~/.claude/context-backups
cat ~/.claude/sessions/$SESSION_ID.json > ~/.claude/context-backups/$SESSION_ID-pre-compact.json
echo "✅ Context 備份到 ~/.claude/context-backups/"
🧩 5 個 Hook 組合模式
單一 hook 解決單一問題——組合 hook 解決工作流問題。
1. 「安全三段式」**:Pre 攔截 + Post 記錄 + StopFailure 警報
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"command": "./.claude/hooks/pre-block-dangerous.sh '${tool.args.command}'"
}],
"PostToolUse": [{
"matcher": "Bash",
"command": "./.claude/hooks/post-log-command.sh '${tool.args.command}'"
}],
"StopFailure": [{
"command": "./.claude/hooks/notify-team-failure.sh '${error}'"
}]
}
}
用途:production 環境的最低防線——攔截 + 留 audit + 即時警報。
2. 「品質閉環」**:Post lint + 失敗自動回饋
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit",
"command": "npx eslint ${tool.args.file_path} 2>&1 || echo 'Lint failed: please fix' >&2"
}]
}
}
用途:Claude 改錯 → 自動看到 lint 報錯 → 自己修——完全 self-healing。
3. 「Audit Trail」**:SessionStart 開 log + 每 PostToolUse 寫入 + SessionEnd 上傳
{
"hooks": {
"SessionStart": [{
"command": "echo \"[$(date)] Session start\" > /var/log/claude/$SESSION_ID.log"
}],
"PostToolUse": [{
"command": "echo \"[$(date)] ${tool.name} ${tool.args}\" >> /var/log/claude/$SESSION_ID.log"
}],
"SessionEnd": [{
"command": "aws s3 cp /var/log/claude/$SESSION_ID.log s3://my-bucket/claude-audit/"
}]
}
}
用途:金融 / 醫療 / 政府客戶的合規必備——完整 audit trail。
4. 「Prod 保護」**:多層 PreToolUse 攔截
#!/bin/bash
# .claude/hooks/multi-layer-prod-guard.sh
CMD=$1
# Layer 1: 分支檢查
BRANCH=$(git branch --show-current)
if [[ "$BRANCH" == "main" ]] && [[ "$CMD" == *"deploy"* ]]; then
echo "BLOCKED: cannot deploy from main directly" >&2
exit 1
fi
# Layer 2: 環境檢查
if [[ "$CMD" == *"--env=prod"* ]]; then
echo "BLOCKED: prod deploy requires manual approval" >&2
exit 1
fi
# Layer 3: 確認檔
if [[ "$CMD" == *"terraform apply"* ]] && ! [[ -f ".prod-deploy-approved" ]]; then
echo "BLOCKED: terraform apply requires .prod-deploy-approved file" >&2
exit 1
fi
5. 「團隊一致性」**:SessionStart 讀規範 + PreToolUse 強制 + StopFailure 回報
{
"hooks": {
"SessionStart": [{
"command": "git pull origin main -- .claude/team-rules.md"
}],
"PreToolUse": [{
"matcher": "Edit",
"command": "cat .claude/team-rules.md >&2"
}],
"StopFailure": [{
"command": "curl -X POST $SLACK_WEBHOOK -d '{\"text\":\"Claude 違反團隊規範:'${error}'\"}'"
}]
}
}
🏢 企業 / 團隊級真實案例(3 個)
案例 1:金融業 — PII redaction + audit log + 跨 region 限制
需求:台灣金融業 + 合規要求:
- 個資不能丟外部 AI(PII redaction)
- 完整 audit trail(誰用 Claude 做了什麼)
- 資料不能跨境(限定 Anthropic 美國 / 歐洲 region)
Hook 組合:
- UserPromptSubmit 掃 PII(範例 10)
- Audit Trail 三段式(模式 3)
- PreToolUse 確認 API endpoint 限定 region(自訂 hook)
案例 2:SaaS 團隊 — 強制 conventional commits + 自動 changelog
需求:敏捷 SaaS 團隊:
- 強制 Conventional Commits(feat: / fix: / chore:)
- 每次 commit 自動 update CHANGELOG.md
- PR 自動產生 description 草稿
Hook 組合:
- PostToolUse Bash(git commit) → 驗證 commit 格式 + update CHANGELOG
- PostToolUse Edit → 追加到 .claude/pr-draft.md(範例 14)
案例 3:Mason 內容站台 — 中文標點全形 + 不准動 jingyun-spa
需求:Mason 自己用 Claude Code 維護 1,600+ 篇站台:
- 中文標點必全形(站台 SEO 一致性)
migration/old-content/跟jingyun-spa專案不准動(私案)- 每篇文章 description 必須 < 160 字(schema 規範)
Hook 組合:
{
"hooks": {
"PreToolUse": [{
"matcher": "Edit|Write",
"command": "if [[ '${tool.args.file_path}' == *migration/old-content/* ]] || [[ '${tool.args.file_path}' == *jingyun-spa* ]]; then echo 'BLOCKED: protected dir' >&2; exit 1; fi"
}],
"PostToolUse": [{
"matcher": "Edit|Write",
"command": "if [[ '${tool.args.file_path}' == *.md ]]; then node scripts/normalize-cjk-punctuation.mjs '${tool.args.file_path}'; fi"
}],
"PostToolUse": [{
"matcher": "Edit|Write",
"command": "if [[ '${tool.args.file_path}' == *.md ]]; then node scripts/check-description-length.mjs '${tool.args.file_path}'; fi"
}]
}
}
這就是 Mason 真實在用的 hook 配置——對應 Claude Code workflow 完整實戰。
⚠️ 5 個反模式(避坑)
反模式 1:Hook 自己又呼叫 Claude
症狀:PostToolUse 裡跑 claude --some-task → 無限迴圈
為什麼:Claude 改檔 → hook 觸發 → hook 呼叫 Claude → 又改檔 → 又觸發
解法:Hook 內絕對不能呼叫 Claude Code 本身。如果需要 AI 處理,用 API 直接呼叫:
# 錯誤
claude "請審視這個檔案"
# 正確
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-d '{"model":"claude-sonnet-4-6","messages":[...]}'
反模式 2:PreToolUse 太嚴 → Claude 卡住
症狀:Claude 想做事,每個動作都被攔——無法完成任務**
為什麼:Allow list 寫太嚴 / Deny 太多
解法:從寬到嚴疊代——先給 Claude 充分權限,碰到風險點再加 hook 攔。不要一開始就鎖死**。
反模式 3:PostToolUse 跑太慢拖死 session
症狀:每次 Edit 後 hook 跑 30 秒——Claude session 變極慢
為什麼:hook 內跑慢任務(全專案 lint、跑整套 test、上傳到外部 API)
解法:Hook 內只跑「快任務」——慢任務改成「寫到 queue,背景 process 處理」:
# 錯誤:同步跑 5 分鐘 test
npm test
# 正確:背景跑 + 快速回應
nohup npm test > /tmp/test.log 2>&1 &
echo "Test queued, results will be in /tmp/test.log"
反模式 4:Hook script 寫死絕對路徑
症狀:自己電腦能跑,換台機器就壞了**
為什麼:寫了 /Users/mason/.claude/hooks/xxx.sh 這種絕對路徑
解法:全用相對路徑或環境變數:
# 錯誤
/Users/mason/.claude/hooks/check.sh
# 正確
$CLAUDE_PROJECT_DIR/.claude/hooks/check.sh
# 或在 hook script 內
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
反模式 5:Hook 出錯但沒 fallback
症狀:Hook 內某個指令失敗,整個 Claude session 直接掛**
為什麼:hook script 沒處理 error,exit 1 直接 propagate 給 Claude**
解法:Hook 內所有可能失敗的指令都加 fallback:
# 錯誤
npx prettier --write file.ts
# 正確
npx prettier --write file.ts 2>/dev/null || echo "Warning: prettier failed, skipping" >&2
Hooks 跟 Skills、MCP 該怎麼搭配?
| 你想做什麼 | 該用哪個 |
|---|---|
| 「這類任務都用這個流程」 | Skill → Claude Skills 完整指南 |
| 「需要連外部系統(DB / API)」 | MCP → MCP 協議入門 |
| 「事件發生時自動做某事」 | Hook ← 本文 |
| 「一鍵啟動的固定流程」 | Slash Command |
典型搭配:Skill 提供 SOP + MCP 連外部系統 + Hook 提供護欄 + Slash Command 一鍵啟動——4 個機制各司其職。
❓ FAQ
Hooks 寫在哪個檔案?全域跟專案優先誰?
3 個位置:
~/.claude/settings.json(全域)——所有 session 都生效<repo>/.claude/settings.json(專案)——只在這個專案生效<repo>/.claude/settings.local.json(個人專案 override)——個人在此專案的覆寫,不 commit
優先順序:local > 專案 > 全域——local 會 override 專案,專案 override 全域。
典型用法:
- 全域:所有專案的安全規則(rm -rf 攔截、PII 掃描)
- 專案:團隊共用的規則(lint / format / commit 格式)
- local:個人偏好(額外的 Slack 通知、log 路徑)
PreToolUse 跟 PostToolUse 該選哪個?
核心差別:Pre 可阻擋,Post 不能阻擋**。
用 PreToolUse:安全攔截——「我要阻止 Claude 做這件事」**
用 PostToolUse:自動化後續——「Claude 做完後我要再做什麼」**
典型搭配:
- PreToolUse:攔截 rm / push —force / 接觸 .env
- PostToolUse:lint / format / 寫 log / 發通知
注意:Post 不能阻擋 = 即使 hook 出錯也擋不住 Claude 的動作——所以重要的安全規則用 Pre。
Hook 出錯會不會把整個 Claude Code 弄壞?
Pre 會 / Post 不會:
PreToolUse:hook 失敗(exit 1) → Claude 整個 tool call 取消——這是它的功能
PostToolUse:hook 失敗 → 記錄錯誤但 Claude session 繼續——不會弄壞
避免「hook 把 session 弄壞」3 個原則**:
- 所有可能失敗的指令加 fallback →
command || true或2>/dev/null - 快任務 Pre / 慢任務 Post → Pre 必須在 < 2 秒回應
- 完整測試——新 hook 加上去後先在獨立專案測試 1-2 天
25 個範例都要裝嗎?從哪 5 個開始?
第一次安裝建議 5 個(對應 pocketos 9 秒刪庫事件):
- 範例 6:攔 rm -rf / sudo / pkill
- 範例 13:攔 git push —force 到 main
- 範例 8:攔接觸 .env / credentials
- 範例 18:SessionStart 寫 audit log
- 範例 1 或 2:PostToolUse lint / prettier
這 5 個 = 80% 的「保命設定」——裝完後再依需求加其他。
進階階段(用 1-2 個月後):
- 範例 10:PII redaction(若處理客戶資料)
- 範例 16:Stop Slack 通知(團隊協作)
- 範例 22:PreToolUse 套用 style guide
Hooks 跟 Skills 該怎麼分工?
核心區別:
Skill 是「怎麼做」的 SOP**:Claude 看到某個任務,用這套 SOP 處理**
Hook 是「事件發生時的自動腳本」:Claude 做某個動作前 / 後,強制執行**
典型分工:
- Skill:「幫使用者寫健康新聞 → 套用編譯方法論」**
- Hook:「改完 .md 檔 → 自動跑全形標點 normalize」**
搭配:Skill 在「對話層」指導 Claude,Hook 在「工具層」強制執行**。
Mason 站台:Skills 處理寫作 SOP,Hooks 處理「改完後一定要做的事」——兩者互補**。
⚠️ 警語
- 本文 25 個範例是 ready-to-copy 但非 production-ready——請依你的環境調整
- Hooks 內呼叫外部 API(Slack / Datadog / Linear)會增加 latency——請評估接受度
- 企業環境設 hooks → 必須跟 IT / 法務確認合規——audit log、PII redaction 都涉及法規
權威來源:
深入閱讀:➜ Claude Code 完全 Pillar | Claude Code 工作流 | Claude Skills 完整指南 | MCP 協議入門