Claude Code Hooks 2026:逐步設定與 7 個可直接使用的範例
Markdown7 個可直接使用的 .claude/settings.json hooks,複製貼上即可:保護 .env、prettier、禁止 force-push 至 main、檢查 em dash,以及開發伺服器。4 種處理器類型和 31 個事件。
閱讀全文
Claude Code 中的 hooks 是 shell 命令、HTTP 呼叫或簡短的 LLM 檢查,會在 Claude 執行動作前後自動執行。它們在特定工具事件增加檢查、編輯後格式化及啟動時注入背景。匹配的 PreToolUse 可拒絕該呼叫;regex 範例只是便利防護,不是涵蓋每種檔案或 Git 路徑的安全邊界。接下來會介紹 4 種處理器類型、31 個生命週期事件、「何時該把規則放進 hook」的測試、7 個可複製到 .claude/settings.json 的 hooks,以及我親身遇過的 3 個陷阱。
Claude Code 中的 hooks 是什麼?
重點摘要
Hook 是 Claude Code 針對特定生命週期事件執行的 shell 命令、HTTP 呼叫、簡短的 LLM 提示或子代理。與模型可能忽略或忘記的 CLAUDE.md 指示不同,hook 每次都會以相同方式執行:它是程式碼,不是建議。
有半年時間,我都把「絕對不要直接編輯 .env」這條規則放在 CLAUDE.md 裡。Claude 每個月都會違反一次。不是故意的;只是在漫長的工作階段結束前忘了這條規則。有一次,.env 裡多了一個 token;另一次,同名變數覆蓋了正在使用的變數。
我把這條規則移到 hook 裡,讓它不可能被忘記。匹配的直接編輯工具提供 .env 路徑時,hook 會拒絕該呼叫;Bash 重導向、腳本、MCP 及其他路徑需另外控制,不代表檔案不可變。
Hooks 是由你定義的 shell 命令、HTTP 端點或 LLM 提示,會在 Claude Code 生命週期的特定時點自動執行。
換句話說,hook 是你設定、讓 Claude Code 在特定事件發生時執行的命令。每次檔案編輯、Bash 啟動、工作階段開始或子代理完成,都是可以掛上 hook 的事件。
對 vibe coder 來說,這很重要,因為你可以:
- 保護含有機密的檔案,避免遭到意外修改。
- 每次產生程式碼後自動執行 prettier/eslint。
- 未經明確許可,禁止對 main 執行
git push --force。 - 在每個工作階段開始時注入最新的脈絡。
- 記錄 Claude 的操作以供稽核。
CLAUDE.md 負責脈絡。Hooks 負責動作。兩者是不同的東西,把它們混為一談是初學者最常犯的錯誤。
Hooks 與 CLAUDE.md、skills 有什麼不同?
重點摘要
CLAUDE.md 是模型在工作階段開始時讀取並嘗試遵循的文字。Hook 是 Claude Code 程序在事件發生時執行的 shell 命令,不依賴模型。Skill 是一組有名稱的指示,Claude 可以透過 Skill 工具呼叫,就像函式一樣。三者分別適用於不同類型的任務。
Anthropic 的文件明確說明了 CLAUDE.md 和 hooks 的差異:
CLAUDE.md 的內容是在系統提示之後,以使用者訊息的形式傳送,而不是系統提示的一部分。Claude 會閱讀並嘗試遵循,但無法保證會嚴格遵守。
換句話說,CLAUDE.md 的內容會以使用者傳來的第一則訊息送達 Claude,而不是系統指示。模型會閱讀並嘗試遵循,但沒有任何保證,尤其是在漫長工作階段的後段,脈絡逐漸膨脹、較早的指示被擠出時。
Hook 的運作方式不同。它位於 settings.json,不依賴模型,而是由 Claude Code 程序在特定事件發生時執行,例如 PreToolUse、PostToolUse、SessionStart 等。命令會在 Claude 收到結果前執行,並能實際阻擋動作(exit 2),或在編輯後修改檔案(exit 0)。
比較如下:
| 功能 | CLAUDE.md | Hook | Skill |
|---|---|---|---|
| 存放位置 | 專案根目錄中的 CLAUDE.md | .claude/settings.json | .claude/skills/ 資料夾 |
| 執行者 | 模型將其視為使用者訊息的一部分來閱讀 | Claude Code 程序在事件發生時執行 | 模型透過 Skill 工具執行 |
| 執行保證 | 無(盡力而為) | 設定且匹配的 command hook 執行程式,失敗與結束行為依事件而異 | 透過模型呼叫(有授權,但不是 shell 層級) |
| 用途 | 專案脈絡、語氣、風格、路由 | 限制、格式化、檢查特定檔案 | 帶有參數的複雜多步驟程序 |
| 執行時機 | 工作階段中的每則模型訊息 | 生命週期事件發生時 | 由模型呼叫時 |
| 能否阻擋動作? | 否 | 可以(exit 2) | 否 |
完整的 CLAUDE.md 範本,以及維護這個檔案的 6 條規則,請參閱指南2026 年如何設定 CLAUDE.md:可直接使用的範本與 6 條規則。如需了解以具名能力形式提供的 skills,請參閱Claude Code Skills:如何建立自己的技能庫。
Hook 是最低層級:沒有模型、沒有解讀,只有 shell。
Hooks 是脈絡工程工具之一,與 Plan Mode 和 /compact 並列。
4 種 hook 類型:Command、HTTP、Prompt、Agent——該選哪一種?
重點摘要
Claude Code 有 4 種處理器類型。Command 是最常見的 shell script。HTTP 會向你的 URL 傳送 POST 請求,適合團隊與整合用途。Prompt 會呼叫簡短的 Haiku,進行語意檢查。Agent 會建立能存取工具的子代理,進行深入驗證。初學者使用 Command 和 Prompt 就夠了,它們涵蓋 90% 的任務。
| 類型 | 功能 | 適用情況 | 複雜度 |
|---|---|---|---|
| Command | 執行 shell 命令,從 stdin 接收 JSON,並以結束代碼和 stdout 回應 | 90% 的情況:檢查路徑、格式化、對內容執行 grep、lsof、git 操作 | 低 |
| HTTP | 向你的 URL 傳送含 JSON 主體的 POST 請求,並接收 JSON 回應 | 規則應由團隊/CI 負責時:集中驗證、共用狀態儀表板、遠端稽核 | 中(需要伺服器) |
| Prompt | 將簡短提示傳給 Haiku(預設),並取得語意評估的 yes/no 結果 | grep 不夠用時:「評估這段文字聽起來是否像企業公文」 | 低 |
| Agent | 建立可存取 Read/Grep/Glob 的子代理,執行多步驟檢查 | 深入驗證:「找出所有變更過的檔案,並確認是否有相對應的測試」 | 高 |
最常見的錯誤,是用 Command 做應該交給 Prompt 的事。舉例來說,「.md 檔案不要使用 em dash」是 grep 能處理的工作,因此 Command 很適合。但「不要用企業腔調寫作」涉及語意,需要 Prompt。
如果你才剛開始,請使用 Command。以下 7 個可直接使用的 hooks 全都是 Command。等你準備好了,再使用 HTTP/Prompt/Agent。
settings.json 在哪裡?4 個設定層級是什麼?
重點摘要
Claude Code 會從 4 個位置讀取設定:企業(透過 MDM 部署)、使用者(~/.claude/settings.json)、專案(儲存庫根目錄的 .claude/settings.json),以及本機(.claude/settings.local.json,不納入 git)。4 個檔案都會在工作階段開始時合併,較低層級的設定優先。
| 層級 | 路徑 | 適用範圍 | 納入 git? |
|---|---|---|---|
| 企業 | /Library/Application Support/ClaudeCode/settings.json(Mac)、/etc/claude-code/settings.json(Linux) | 透過 MDM 套用至整個團隊 | 否 |
| 使用者 | ~/.claude/settings.json | 單一使用者的所有專案 | 否 |
| 專案 | 儲存庫根目錄中的 .claude/settings.json | 整個團隊的這個專案 | 是 |
| 本機 | .claude/settings.local.json | 僅限這個使用者的這個專案 | 否(列在 .gitignore) |
我把下文介紹的 7 個 hooks 放在專案的 .claude/settings.json,因此複製儲存庫後便會自動包含,Claude 在新的視窗中也會遵循相同規則。個人的實驗性 hooks 則放在 .claude/settings.local.json,以免影響團隊。
4 個檔案都會在工作階段開始時合併。如果規則衝突,較低層級優先(本機優先於專案,專案優先於使用者)。
建立第一個 hook 最快的方法,是在 Claude Code 中直接使用互動式 /hooks 選單。從清單選擇事件、選擇 matcher(例如 Bash 或 Edit),然後貼上命令。Claude Code 會替你在 settings.json 寫入有效的 JSON。
31 個生命週期事件:初學者需要哪些?
重點摘要
Claude Code 目前有 31 個生命週期事件,分成 3 種頻率:每個工作階段一次(SessionStart/SessionEnd)、每個回合一次(UserPromptSubmit/Stop),以及每次工具呼叫時(PreToolUse/PostToolUse)。初學者需要 31 個事件中的 4 個:限制操作用 PreToolUse、自動編輯用 PostToolUse、脈絡用 SessionStart,最後檢查用 Stop。
事件分成三種頻率:每個工作階段一次(SessionStart、SessionEnd)、每個回合一次(UserPromptSubmit、Stop、StopFailure),以及代理循環中的每次工具呼叫時(PreToolUse、PostToolUse)。
了解這三種頻率很重要,才能避免混淆:
- 每個工作階段一次 -
SessionStart、SessionEnd、Setup。Claude Code 開啟和關閉時各執行一次。可用來初始化脈絡及清除暫存檔案。 - 每個回合一次 -
UserPromptSubmit、UserPromptExpansion、Stop、StopFailure。你每次傳送訊息,以及模型每次停止時都會觸發。可用來記錄對話和檢查最終結果。 - 每次工具呼叫時 -
PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest、PermissionDenied、PostToolBatch、SubagentStart、SubagentStop。每次編輯檔案、啟動 Bash、讀取或搜尋時都會觸發。這是最頻繁的一層,限制、格式化和驗證都在這裡執行。
完整的 31 個事件清單還包含非同步/反應式事件:TeammateIdle、PreCompact、PostCompact、Elicitation、ElicitationResult、InstructionsLoaded、ConfigChange、CwdChanged、FileChanged、WorktreeCreate、WorktreeRemove、Notification。這些適用於進階情境,現在先略過。
五個問題測試:何時該把規則放進 hook,何時留在 CLAUDE.md?
重點摘要
如果規則涉及動作(而非脈絡)、關乎安全/金錢/資料、可以用 grep/regex 檢查、違反後難以復原,而且 Claude 至少已經違反過一次,就應該放進 hook。如果這五項中回答「是」的不到三項,就留在 CLAUDE.md。我用這個測試檢查了自己的 80 行規則,並將 5 條移到 hooks。
對我來說,這就像法律,而不是建議。我把 CLAUDE.md 和 hook 之間的選擇,濃縮成簡單的五個問題。如果至少有三個答案是「是」,這條規則就放進 hook。不到三個,就留在文字檔裡。
「何時該把規則放進 hook?」測試:
- 這條規則涉及的是動作,而不是脈絡嗎?(「不要執行
rm -rf」是動作;「我有一個 TypeScript 專案」是脈絡。) - 這條規則關乎安全、金錢或資料嗎?(「不要碰
.env」、「不要公開金鑰」。) - 可以用 grep、regex 或 shell 命令的結束代碼檢查嗎?(「編輯
.md後,檢查是否有 em dash。」) - 如果違反這條規則,後果會很難復原嗎?(「對 main 執行 force push」很難復原;「忘了在句尾加句號」則很容易。)
- 你是否已經看過 Claude 至少忘記這條規則一次?
範例:「不要用 em dash,只用連字號。」 涉及動作?是。關乎安全?否。可以用 grep 檢查?是。難以復原?否。看過違規?是。5 項中有 3 項:放進 hook。
反例:「我使用的是 vibe coder,不是開發者;請不要用俚語寫作。」 涉及動作?比較像脈絡。關乎安全?否。可以用 regex 檢查?太難。難以復原?否。看過違規?是。5 項中有 1 項:留在 CLAUDE.md。
我的規則中有 5 條通過測試,因此移到了 hooks。其餘 75% 留在文字檔裡。
7 個可直接複製的 hooks
重點摘要
以下是 7 個可直接用於 .claude/settings.json 的 hooks:阻擋編輯 .env、自動執行 prettier、禁止對 main 執行 force-push、檢查 em dash、在 npm run dev 前釋放連接埠、測試成功後自動提交,以及在工作階段開始時注入最新脈絡。把每個 hook 複製到檔案中,重新啟動 Claude Code,就能使用。
7 個 hooks 都放在專案根目錄的同一個 .claude/settings.json 檔案中。每個項目都有事件類型、工具篩選器(matcher)和 shell 命令。Exit 2 可在執行前拒絕匹配 PreToolUse;PostToolUse 中動作已發生,hook 只能回報,不能撤回。
各事件的 exit 2 行為不同,請依 官方 hooks 參考 查核;PreToolUse 能阻擋呼叫,PostToolUse 不會撤回已發生編輯。
exit 0 代表成功。exit 1 代表發生錯誤,但 Claude 會繼續(這是下文第一個反模式)。exit 2 依事件而異,可阻擋 PreToolUse,PostToolUse 只回報已發生動作的結果。
Hook 1. 檢查直接編輯 .env(PreToolUse、exit 2)
封鎖直接編輯任何符合 .env、.env.local、.env.production 等名稱模式的檔案。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '(^|/)\\.env(\\.|$)' && { echo 'Do not edit `.env` directly. Tell me which variable to add, and I’ll open the file myself.' >&2; exit 2; } || exit 0"
}
]
}
]
}
}
功能: 每次 Edit/Write/MultiEdit 執行前,它會取得檔案路徑,並用 grep 檢查是否符合 .env 模式。如果符合,就回傳 exit 2 和一則訊息。Claude 會看到 stderr 並告訴我:「我嘗試編輯 .env,但被 hook 擋下來了;告訴我該新增哪個變數。」
Hook 2. 每次編輯程式碼後自動執行 prettier(PostToolUse、exit 0)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(ts|tsx|js|jsx|json)$' && npx prettier --write \"$FILE\" 2>/dev/null & exit 0"
}
]
}
]
}
}
功能: 每次 Edit/Write 後,它會取得路徑;若副檔名是 .ts/.tsx/.js/.jsx/.json,就用 & 在背景執行 prettier。因為這是格式化而非限制,所以永遠回傳 exit 0。即使 prettier 失敗也沒關係,檔案會維持原樣。
與 hook 1 的差異至關重要:PreToolUse + exit 2 會在動作之前強制封鎖。PostToolUse + exit 0 則會在動作之後進行柔性編輯。這是適用於不同任務的兩種模式。
Hook 3. 封鎖對 main 分支執行 git push --force(PreToolUse、exit 2)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE 'git[[:space:]]+push' && echo \"$CMD\" | grep -qE '(\\-\\-force|\\-\\-force-with-lease|[[:space:]]-f([[:space:]]|$))' && echo \"$CMD\" | grep -qE '([[:space:]/:])main([[:space:]/:]|$)' && { echo 'Force push to main is forbidden. Create a new branch or ask me to do it manually.' >&2; exit 2; } || exit 0"
}
]
}
]
}
}
功能: 每次啟動 Bash 時,都會檢查命令是否包含 push、--force/--force-with-lease/-f,以及 main。三個條件都符合時,就會顯示清楚的訊息並封鎖操作。
以前我把這條規則用全大寫寫在 CLAUDE.md 裡。Claude 仍然大約每兩週就會違反一次,尤其是我叫它「force push 來復原錯誤的 commit」時。Hook 只抓列出的字面指令,別名、腳本、隱含目的分支或其他 Git client 可繞過字串檢查。遠端應強制保護 main,並限制代理憑證。
Hook 4. 封鎖文字檔中的 em dash(PostToolUse、exit 2)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "FILE=$(jq -r '.tool_input.file_path // empty'); [ -z \"$FILE\" ] && exit 0; echo \"$FILE\" | grep -qE '\\.(md|mdx|tsx?)$' && grep -q $'\\xe2\\x80\\x94' \"$FILE\" && { echo \"File $FILE contains an em dash (U+2014). Replace it with a regular hyphen (U+002D).\" >&2; exit 2; } || exit 0"
}
]
}
]
}
}
功能: PostToolUse 的 exit 2 在編輯後回報檢查失敗,不會還原或防止寫入。如果 Claude 編輯檔案後仍留有 em dash,hook 就會失敗、向 Claude 提供回饋,讓它再試一次。經過 2–3 個工作階段後,Claude 就不再插入這個字元。
請注意,grep 使用 $'\xe2\x80\x94' 專門搜尋 em dash 字元(U+2014);這是 Bash 中以 UTF-8 位元組表示字元的寫法。如果你只寫 grep -q '-',一般連字號也會被找到,導致每個檔案都執行檢查。一個看不見的字元就會破壞整套邏輯。
Hook 5. 在執行 npm run dev 前釋放連接埠 3000(PreToolUse、exit 0)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "CMD=$(jq -r '.tool_input.command // empty'); [ -z \"$CMD\" ] && exit 0; echo \"$CMD\" | grep -qE '(npm|pnpm|yarn)[[:space:]]+run[[:space:]]+dev' && { lsof -ti:3000 | xargs -r kill -9 2>/dev/null; echo 'Port 3000 freed before launch' >&2; }; exit 0"
}
]
}
]
}
}
功能: 每次執行 npm run dev(或 pnpm/yarn)前,都會終止占用連接埠 3000 的程序。因為這是準備工作而非限制,所以永遠回傳 exit 0。這能避免一種常見陷阱:Claude 看到「port in use」,以為是程式碼出問題而開始修程式碼,但其實只是舊伺服器仍在執行。
Hook 6. 測試成功後自動提交(PostToolUse、exit 0)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "INPUT=$(cat); CMD=$(printf '%s' \"$INPUT\" | jq -r '.tool_input.command // empty') || exit 1; EXIT=$(printf '%s' \"$INPUT\" | jq -r '.tool_response.exit_code // empty') || exit 1; case \"$CMD\" in 'npm test'|'pnpm test'|'yarn test') ;; *) exit 0 ;; esac; [ \"$EXIT\" = \"0\" ] || exit 0; git diff --cached --quiet || { echo 'Existing staged changes: review manually' >&2; exit 0; }; git add -- src/orders.ts tests/orders.test.ts || exit 1; git diff --cached --quiet && exit 0; git commit -m 'tests: checkpoint after successful run'"
}
]
}
]
}
}
功能: 只讀一次 JSON stdin,只接受列出的完整普通測試指令,且 tool_response.exit_code 明確為零才繼續。版本沒有該欄位時略過提交,不能猜成功。src/orders.ts、tests/orders.test.ts 換任務確切 owned 檔案;已有 staged 變更就停,避免納入無關工作。add 失敗停止、空 diff 不提交,正常 commit hooks 保留。啟用前離線測成功、失敗及缺少結果輸入。
反模式是在每次 Edit 後加入 git commit --no-verify。保持提交檢查;若檢查遞迴觸發自動化,應限縮觸發或重設檢查,不預設略過驗證。啟用前審查 staged diff 與任務所有權。
Hook 7. 在工作階段開始時注入最新脈絡(SessionStart 搭配 additionalContext)
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo 'no-branch'); STATUS=$(git status --short 2>/dev/null | head -10); echo \"{\\\"additionalContext\\\":\\\"Current branch: $BRANCH. Uncommitted changes:\\n$STATUS\\\"}\""
}
]
}
]
}
}
功能: 在工作階段開始時,它會收集目前的 Git 分支和未提交變更清單,然後回傳含有 additionalContext 欄位的 JSON。Claude Code 會把這段文字注入第一則系統訊息,因此 Claude 從一開始就知道目前所在位置,以及上個工作階段留下了什麼。
請使用 additionalContext 提供 Claude 應該知道的環境目前狀態,或剛完成操作的資訊:環境狀態(目前分支、部署目標、啟用中的功能旗標)、條件式專案規則(剛編輯的檔案適用哪個測試命令)、外部資料(指派給你的未結問題、近期 CI 結果)。
這個 hook 還能擴充為取得最新 commit 和說明、GitHub Actions 狀態、Linear/Jira 中目前的 sprint,以及目前儲存庫的未結問題。
每兩個人就有一個人會遇到的 3 個 hook 陷阱
重點摘要
第一個陷阱:exit 1 的 hook 會無聲地放行,Claude 也會忽略錯誤。第二個陷阱:hook 中的繁重工作會在 60 秒逾時後被終止。第三個陷阱:hook 只能看到 ~/.zshrc 中的環境變數,看不到專案的 .env。每個問題都能用一行解決,但如果不知道這些陷阱,就可能以為「hooks 不會動」。
陷阱 1. exit 1 的 hook 會無聲地放行
這是最危險的一個。有一週我以為 .env 保護 hook 正常運作,後來才發現我在 hook 中使用了 jq -r '.tool_input.file_path',卻沒有檢查 null。當 tool_input 為空時,jq 以代碼 1 結束。Claude 把 exit 1 當成「hook 發生錯誤,繼續執行」,因此動作照常進行。我不明白為什麼 .env 還是會被碰到。直到我在終端機用空輸入手動執行 hook,才找到原因。
修正方法只有一條規則: 每個 hook 一開始都要檢查 null,並以 || exit 0 結尾。這樣任何意外的結束都會以「安全」方式退出,不會無聲放行。
# Safe start and end template:
FILE=$(jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
# ... your logic ...
exit 0 # or || exit 0 on one line
陷阱 2. 執行緩慢的 hooks 會因逾時而被終止
我曾設定一個 hook,在 Edit 後對整個檔案執行 eslint --fix。大型檔案(1000+ 行)需要 8–10 秒處理。我知道 Claude Code 的預設 hook 逾時時間是 60 秒,所以以為自己沒有超出限制。但如果一分鐘內發生多次 Edit,hooks 會累積並排隊,有些會直接被略過。直到日誌顯示「hook timed out」,我才發現這點。
修正方法: 如果 hook 需要執行繁重工作,就在背景執行。使用 eslint --fix "$FILE" &,不要等待結果。這樣 hook 就會在幾毫秒內回傳 exit 0,而實際工作則在背景執行。
# Heavy operation in background:
npx eslint --fix "$FILE" &
exit 0
陷阱 3. Hook 看不到專案環境變數
我以為 hook 中的 process.env 會像 Node.js 應用程式一樣可用,但事實並非如此。Hook 會在 Claude Code 父程序啟動的 shell 中執行,只能看到 ~/.zshrc 或 ~/.bashrc 中的內容,以及 Claude Code 明確傳入的變數。如果你的 hook 依賴 .env 中的 OPENAI_API_KEY,就必須手動讀取 .env。
修正方法: 在 hook 命令中明確讀取 .env。
# Passing project env into hook:
cd "$PWD" && source .env && curl -H "Authorization: Bearer $OPENAI_API_KEY" ...
# Or with dotenv:
dotenv -e .env -- node my-validator.js
在每個套件都有自己 .env 檔案的 monorepo 中,這個問題尤其麻煩。可透過傳入 PWD,並在命令開頭明確執行 cd "$PWD" 來解決。
提交前如何測試 hooks?
重點摘要
Hook 就是 shell 命令。你可以在 Claude Code 執行前,先在終端機用假的 stdin 手動執行、檢查結束代碼和 stderr。這能省下好幾個小時的除錯時間,也能避免 hook 封鎖自己的操作。
基本測試情境。 從 JSON 中取出 hook 命令,開啟終端機,並輸入 Claude Code 會傳送的 JSON:
# Test hook 1 (protect .env):
echo '{"tool_input":{"file_path":".env"}}' | bash -c '
FILE=$(jq -r ".tool_input.file_path // empty")
[ -z "$FILE" ] && exit 0
echo "$FILE" | grep -qE "(^|/)\.env(\.|$)" && {
echo "BLOCKED" >&2
exit 2
} || exit 0
'
echo "Exit code: $?"
你應該會在 stderr 看到 BLOCKED 和 Exit code: 2。如果得到 exit 0,代表你的 regex 沒有作用;請修正 grep。
需要檢查的情境:
- 正向情境 - 檔案應該被封鎖。Hook 1 使用
.env,hook 3 使用git push --force main。 - 反向情境 - 檔案不應該被封鎖。Hook 1 使用
README.md,hook 3 使用git push origin feature-branch。 - 邊界情況 - 空輸入、null 值、非預期字元。Hook 應該回傳 exit 0,或以明確錯誤失敗,而不是無聲放行。
實驗性 hooks 放置位置。 不要放在 .claude/settings.json,而是放在 .claude/settings.local.json(不納入 git)。在 PostToolUse 下新增 hooks,以 echo 取代實際動作;確認它們會觸發後,再移到正式的 settings.json。
// Experiment in settings.local.json - writes to /tmp/hook-debug.log:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "echo \"[hook fired] $(date) $(jq -r '.tool_input.file_path // \"?\"')\" >> /tmp/hook-debug.log"
}
]
}
]
}
}
在 Claude Code 中編輯 5–10 次後,開啟 /tmp/hook-debug.log,查看哪些檔案觸發了 hook。如果檔案是空的,代表 matcher 不符合;請修正 regex。
接下來呢?
重點摘要
先從回傳 exit 0 的 PostToolUse hooks 開始;這是最安全的做法,也不會封鎖任何操作。一週後,再加入第一個以 exit 2 結束的 PreToolUse hook,保護 .env 或禁止 force-push。兩週後,CLAUDE.md 會從 200 行的契約縮減成 50–80 行的簡短脈絡說明,將部分事件檢查移到 hooks,硬存取限制另由權限、sandbox 及遠端保護強制執行。
接下來一週的 5 個步驟:
- 用 Grep 搜尋你的
CLAUDE.md,找出「never」、「forbidden」、「always」和「must」等字詞。每條這類規則都是 hook 的候選項目。如果規則帶有命令語氣,文字檔不會強制執行它。 - 從 PostToolUse + exit 0 開始。 這是最安全的做法:會修改內容,但不會封鎖任何操作。Prettier(hook 2)、終止開發伺服器(hook 5)、測試後自動提交(hook 6)。
- 等你有把握時,再加入第一個 PreToolUse + exit 2。 例如保護
.env(hook 1)或禁止 force-push(hook 3)。這是硬性封鎖,如果 grep 命令寫錯,你自己也可能被封鎖。 - 提交前手動測試 hooks(請參閱上方章節)。這能省下好幾個小時的除錯時間。
- 用
settings.local.json進行實驗。 在這裡新增 hooks,以 echo 取代實際動作。
設定好 hooks 後,這個主題群集的下一步是如何在 Claude Code 中復原變更,以防 hook 放過了危險操作。如果你想讓 Claude 在不同工作階段之間記住專案結構,可以在 Claude Code 中建立第二大腦(business/ 資料夾+連結的知識庫)。若要替專案的 skills 設定名稱以便重複使用,請參閱Claude Code Skills。
來源
如果你需要透過 Ailoot 使用神經網路,請看看 Ailoot。
更多詳情
常見問題:Claude Code Hooks 2026:逐步設定與 7 個可直接使用的範例
Hook 為什麼只能讀一次 JSON stdin?
Stdin 是串流,首個 jq 會消耗,後面讀到 EOF。先保存一次輸入,再解析指令與結果;缺少或未知結果視為未驗證,不自動提交。
範例 regex hooks 能建立安全邊界嗎?
不能。它們檢查特定事件及可辨識字串,shell 重導向、別名、腳本、MCP 或其他寫入路徑可能繞過。硬限制需受限憑證、檔案權、sandbox 與遠端保護分支。