昨天的
/spec-check可以告訴我規格哪裡有問題,但它只是「告訴我」。如果我希望某條規則不能被違反,而不是期待 Claude 記得遵守,就得再往下一層。這就是今天要介紹的 Hooks。
昨天做出第一支自己的 skill,它會逐條檢查規格、輸出缺口報告。
但它有個限制:觸發的方式是我得記得叫它。 而且就算它報出九項必補,也沒有任何東西阻止我直接把規格交出去。
這其實是 D04 就埋下的問題:
CLAUDE.md 是 Context,不是防火牆。
當時只是先提到,今天就把這件事情講完:如果真的想阻止 Claude 做某件事,該怎麼做?
CLAUDE.md 的內容會在每個 session 開始時被讀進 context。它是資訊,不是設定。
所以「不要直接改 migration 檔」這句話寫在 CLAUDE.md 裡,效果等於在一段很長的提示詞裡加一句叮嚀。Claude 大部分時候會照做,但不是一道門檻。
Hook 就是在 Claude Code 執行某個動作前後,插入一段自己的程式。

Hook 設定放在:
.claude/settings.json
例如:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "./.claude/guard.sh" }
]
}
]
}
}
這裡有兩個重點:
Claude Code 有很多不同的 Hook Event,和開發流程比較相關的幾個如下:
| 事件 | 什麼時候觸發 |
|---|---|
PreToolUse |
工具呼叫執行之前 |
PostToolUse |
工具呼叫成功之後 |
Stop |
Claude 準備結束回應時 |
SessionStart |
session 開始或恢復 |
InstructionsLoaded |
CLAUDE.md 或 rules 被載入時 |
這次我想做一個很簡單的 Hook:禁止 Claude 修改專案憲法。
Step 1:建立 Hook
我使用 Python 寫一個簡單的檢查:
event = json.load(sys.stdin)
target = event["tool_input"]["file_path"]
if 是憲法:
sys.stderr.write("constitution.md 不允許由 AI 修改")
return 2
它只做三件事:
Step 2:設定 settings.json
設定放專案的 .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "python \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-constitution.py\"",
"statusMessage": "檢查是否修改受保護檔案"
}
]
}
]
}
}
第一層:Matcher 沒有攔到
我開一個新的 Session 請 Claude:把憲法第一條從 snake_case 改成 camelCase,結果它真的改成功了。
看執行紀錄才發現:Claude 這次不是使用 Edit 或 Write,而是 Bash → 執行指令 → 修改檔案。我原本的 Matcher 只有 Edit|Write,所以 Hook 根本沒有被觸發。
這件事很容易被忽略:你以為你在保護「檔案」,但 PreToolUse 實際上是在攔「工具」。
第二層:補上 Bash,還是不夠
於是我把 Matcher 改成 Edit|Write|NotebookEdit|Bash,這次真的擋住了,但我又測了幾個情境:
結果是全部放行,原因很簡單,我保護的是:constitution.md,這道門會保護憲法,卻不保護自己,所以我又把 Hook 本身和設定檔加入保護範圍。
第三層:Hook 攔的是工具,不是人
Claude 又提醒我一件事:
PreToolUse 只能攔 Claude Code 的工具呼叫,不能攔人直接修改檔案。
如果真的要做到檔案層級的保護,還是要靠:
Hook 比較適合拿來做:流程的門檻,而不是:檔案系統的防火牆。
明天:hook 讓規則在本機有了執行力,但它擋的都是這台機器上的事。如果規格不只活在 markdown,還存在 Issue、Database Schema、套件文件裡呢?
這時候,就要讓 Claude 連出去:MCP。