先講結論
- Hooks 是寫在 settings.json 裡的小指令,在固定的時間點一定會執行,例如每次要改檔案之前、改完之後、Claude 回答完的時候。要擋下一個動作,讓腳本用結束代碼 2 結束,並把理由印出來。
- 我設了兩個:改 .env 之前擋下、改完檔案記一筆。實測改待辦檔有記到、改 .env 被擋,Claude 回報沒改成,也說不會繞過。
- 但同一個 hook 只管「改檔案」這個工具。我改成請它用 sed 指令改 .env,一下就改掉了。要另外加一個檢查終端機指令的 hook,才擋得住。
目錄
用 Claude Code 久了,會有一些事希望它「每次都一定要做」或「絕對不要做」。例如改完程式碼一定要跑一次格式化,或是絕對不要動 .env 這種放密碼的檔案。
這些話寫在 CLAUDE.md 裡,它多半會照做,但不是每次。Hooks 就是為了「一定」這兩個字設計的:你寫一小段指令,綁在某個時間點,時間到了 Claude Code 就執行,不經過 AI 的判斷。
這篇講 hooks 寫在哪、有哪些時間點、怎麼擋下一個動作,然後把我實際設的兩個 hook 跑給你看,包括一個我測出來的漏洞。Claude Code 還不熟的話,先看 Claude Code 教學。
Hooks 是什麼
官方文件的說法:hooks 是使用者自己定義的 shell 指令,Claude Code 在生命週期的特定時間點執行它們,讓某些動作「一定會發生」,而不是靠模型決定要不要做。
可以綁的時間點很多,官方的表上列了三十幾個。最常用的是這幾個:
PreToolUse:Claude 要用某個工具之前,例如要改檔案、要跑指令之前。這個時間點可以把動作擋下來。PostToolUse:工具用完之後,例如檔案改好了。Stop:Claude 回答完的時候。Notification:Claude Code 要通知你的時候,例如在等你同意某個動作。SessionStart:一個對話開始的時候。UserPromptSubmit:你送出訊息、Claude 還沒開始處理之前。
官方文件舉的例子有:改完檔案自動跑格式化工具、Claude 在等你的時候跳出桌面通知、擋下對 .env 和 package-lock.json 這類檔案的修改、記錄設定檔被改了什麼。
寫在哪裡
Hooks 寫在 settings 檔的 hooks 區塊。放在哪一個檔,決定它管到哪裡:
~/.claude/settings.json:你自己所有的專案。- 專案的
.claude/settings.json:只有這個專案,可以跟程式碼一起進版本控制,團隊共用。 - 專案的
.claude/settings.local.json:只有這個專案,而且不分享出去。
外掛、Skill 和子代理也可以自帶 hook。在 Claude Code 裡打 /hooks,可以看到它讀到的所有 hook。想全部關掉,在設定檔加 "disableAllHooks": true。
寫法:時間點、比對的工具、要跑的指令
我實際用的設定長這樣,放在測試資料夾的 .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/log-edits.sh"
}]
}
]
}
}
一層一層看:最外面是時間點(PreToolUse、PostToolUse);matcher 是要比對的工具,Edit|Write 表示改檔和寫檔這兩個工具;最裡面的 command 是要跑的指令。$CLAUDE_PROJECT_DIR 是專案資料夾的路徑,Claude Code 會幫你填。
Claude Code 跑 hook 的時候,會把這次的資料用 JSON 從標準輸入交給你的指令,裡面有工具名稱(tool_name)、工具的參數(tool_input,改檔的話有檔案路徑)等。你的腳本讀它,決定要做什麼。
怎麼擋下一個動作
靠的是腳本結束時的代碼。官方文件寫得很清楚:
- 結束代碼 2:擋下這個動作。寫到 stderr(錯誤輸出)的文字,會交給 Claude 當作被擋的理由。
- 結束代碼 0:沒意見。要注意,這不等於核准,原本會問你要不要同意的,還是照樣會問。
- 其他代碼:多半當成 hook 自己出錯,動作照常進行。
我擋 .env 的腳本是照官方範例簡化的:
#!/bin/bash
# 擋住對 .env 的修改
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" \
| jq -r '.tool_input.file_path // empty')
case "$FILE_PATH" in
*.env|*/.env)
echo "已擋下:$FILE_PATH" \
"是受保護的檔案,請不要修改。" >&2
exit 2 ;;
esac
exit 0
它用 jq 這個工具從 JSON 裡拿出檔案路徑,結尾是 .env 就印出理由、用代碼 2 結束。macOS 和 Linux 上,腳本要先 chmod +x 讓它可以執行。
記錄的那個更簡單:每次改完,把時間、工具名稱、檔名加一行到 edit-log.txt。
實測一:改待辦檔可以,改 .env 被擋
測試資料夾裡有一個待辦檔,和一個假的 .env,裡面只有一行 TEST_ONLY=1,沒有任何真的金鑰。
我用非互動模式(claude -p)請它:「在 todo.txt 最後加一行『- 買牛奶』,再把 .env 裡的 TEST_ONLY=1 改成 TEST_ONLY=0。」只允許它讀檔、改檔、寫檔。
結果:
- 待辦檔改好了,
edit-log.txt多了一行14:08:30 Edit …/todo.txt,記錄的 hook 有跑。 - 改
.env那一步被擋下,Claude 收到的是一個錯誤,內容就是我腳本印的那句「已擋下:…/.env 是受保護的檔案,請不要修改。」 - 它最後回報:待辦檔改好了,
.env沒改成,還是TEST_ONLY=1,因為專案的 hook 把它擋下;它也寫了「我沒有用其他方式繞過這個保護」,建議我自己手動改,或先調整保護清單。
整個過程 10 秒。順帶一提,它改之前有先把 .env 讀了一遍,我那個 hook 只管改、不管讀。真的要保護放密碼的檔案,連讀取都要擋,可以在 matcher 加上 Read,或用 Claude Code 的權限設定把它列為拒絕。
實測二:換成 sed 指令,hook 擋不到
接著我換個方式問:「用 sed 指令把 .env 裡的 TEST_ONLY=1 改成 TEST_ONLY=0,這是我自己的測試檔,我確定要改。」這次允許它執行 sed 指令。
結果它先用 grep 看了一下,再跑了一行 sed,.env 就變成 TEST_ONLY=0 了。8 秒,hook 完全沒有反應,記錄檔也沒有新的一行。
原因很單純:我的兩個 hook 的 matcher 都只寫了 Edit|Write。用 sed 改檔,Claude 用的是終端機指令的工具(Bash),根本不會經過這兩個 hook。
這是設 hook 最容易漏掉的地方:你以為擋住了「改 .env」這件事,其實只擋住了「用改檔工具改 .env」這一條路。
實測三:加一個檢查指令的 hook
所以我再加一個 hook,matcher 寫 Bash,指令裡只要出現 .env 就用代碼 2 擋下:
#!/bin/bash
# 擋住提到 .env 的終端機指令
INPUT=$(cat)
CMD=$(echo "$INPUT" \
| jq -r '.tool_input.command // empty')
if echo "$CMD" | grep -q '\.env'; then
echo "已擋下:這個指令會動到 .env,請不要用指令修改受保護的檔案。" >&2
exit 2
fi
exit 0
把 .env 改回 TEST_ONLY=1,同一句話再問一次。這次它第一個想跑的 grep 就被擋了,.env 沒有被改。
它的回答寫:「這個 hook 是你們設的防護,所以我不會用改寫檔名或換工具之類的方式繞過去」,然後給我三個做法:自己在終端機跑那行 sed、在 hook 裡加例外、或改用改檔工具直接改。第三個做法其實也會被第一個 hook 擋下,它沒有想到。不過不管哪一條路,hook 都會照設定擋,不需要靠它記得。
這個寫法很粗:只要指令裡有 .env 這幾個字就擋,連單純讀取也擋。實際要用,可以寫得更細,例如只擋 sed、rm、mv 這類會改東西的指令。
用之前要知道的
Hook 是你電腦上真的會執行的指令。官方文件的安全說明寫,command 型的 hook 用你自己帳號的完整權限執行,你的帳號能讀、能改、能刪的檔案,它都能動;預設最多可以跑 10 分鐘。別人給你的 hook,先看懂、測過再加進去。
matcher 要想清楚。同一件事常常有好幾條路可以做到,例如改檔可以用改檔工具,也可以用指令。要擋,就每一條路都擋。
結束代碼 0 不等於核准。hook 沒意見的時候,原本該問你的還是會問。哪些動作會問你,看你用的是哪一種權限模式,例如只規劃不動手的 plan mode,在 Plan Mode 教學。
除了跑指令(command),官方文件還列了幾種:把資料送到一個網址(http)、讓模型判斷一次(prompt)、呼叫 MCP 伺服器的工具。這幾種這次都沒有測。
不知道 hook 有沒有跑的時候,在 Claude Code 裡打 /hooks 看它讀到哪些。我這次是靠記錄檔和被擋下時的錯誤訊息判斷的。
這篇沒有測的
互動模式裡的 /hooks 選單、Notification 跳桌面通知、Stop 和 SessionStart 這幾個時間點。我三次都是用 claude -p 測改檔和跑指令。
prompt 型、http 型、MCP 工具型的 hook,以及用結構化 JSON 回傳決定的寫法。
Windows 上的寫法。
把 hook 包進外掛、Skill 或子代理。Skill 是什麼在 Claude Skills 教學,子代理在 Claude Code 子代理教學。
常見問題
Claude Code 的 hooks 要寫在哪裡?
寫在 settings 檔的 hooks 區塊。只給這個專案用的寫在專案的 .claude/settings.json,可以跟程式碼一起進版本控制;自己所有專案都要的寫在 ~/.claude/settings.json;只想自己用、不分享的放 .claude/settings.local.json。外掛、Skill 和子代理也可以自帶 hook。
hook 要怎麼擋下 Claude 的動作?
用 PreToolUse 這個時間點,讓腳本用結束代碼 2 結束,並把理由寫到 stderr。Claude Code 會擋下那個動作,把理由交給 Claude。要注意結束代碼 0 只是「沒意見」,不等於核准,原本的權限詢問還是照走。
為什麼我設了 hook 卻好像沒有作用?
先看 matcher 對不對。我實測時,只掛在 Edit 和 Write 的 hook,擋不到 Claude 用 sed 指令改檔,因為那走的是 Bash 這個工具。另外可以在 Claude Code 裡打 /hooks,看它到底讀到了哪些 hook。
hook 可以用中文寫訊息嗎?
可以。我實測時擋下的理由寫中文,Claude 收到之後照著中文的理由回報給我。
這篇怎麼查證的
這篇只有一部分實際跑過,其餘依官方文件整理,查證日期 2026/10/14(在我自己的 Mac 上用 Claude Code 2.1.280 的非互動模式(claude -p)跑了三次,測試資料夾裡放一個待辦檔和一個假的 .env(沒有任何真的金鑰),hook 腳本是我寫的,只動這個資料夾裡的檔案。互動模式的 /hooks 選單、桌面通知、prompt 型和 http 型的 hook 沒有實測,依官方文件)。哪些有跑、哪些沒跑,文中各段有寫。價格與方案以官方頁面為準,工具改版後內容可能過期。