
Claude Code 的 hooks 是在固定時機自動執行的指令,例如改完檔案就跑格式化、執行指令前先檢查。權限規則用 allow、ask、deny 決定動作要免問、要先問,還是一律禁止。CLAUDE.md 裡的規則只是給 Claude 參考;要確定擋下,就用 PreToolUse hook 回傳 exit code 2,或寫 deny 規則。
本文的例子都套在一個假設的專案上:假設你在做一個記帳網站,叫做豪享記帳,前端是 Next.js、後端是 Go。權限模式與基本的 allow 寫法,請看Claude Code 是什麼?。
Hooks、權限和 CLAUDE.md 差在哪?
差別在「誰執行」。CLAUDE.md 和 skill 是寫給 Claude 看的說明,Claude 會參考,但不保證每次照做。權限規則與 hooks 由 Claude Code 程式執行,不靠模型自己記得。
四種管控方式比較
| 方式 | 由誰執行 | 能不能擋下動作 | 適合做什麼 |
|---|---|---|---|
| CLAUDE.md、skill | Claude 自己參考 | 不保證 | 專案慣例、程式與寫作風格 |
| 權限規則 | Claude Code | 能,deny 直接拒絕 | 工具與指令要免問、要問或禁止 |
| hooks | Claude Code 在固定時機執行你的指令 | PreToolUse 可以 | 格式化、檢查、記錄、擋特定指令 |
| 沙箱 | 作業系統 | 能,限制檔案與網路 | Bash 指令只碰得到允許的範圍 |
資料來源:Claude Code 官方文件的權限與hooks 指南頁
判斷方法是:「希望 Claude 這樣做」寫進 CLAUDE.md;「一定要發生」或「一定不能發生」,交給 hooks 和權限規則。CLAUDE.md 的寫法請看CLAUDE.md 要寫什麼?。
Claude 每次要用工具,都會依序經過下面幾關:
一次工具呼叫依序經過執行前 hook、權限規則、執行工具與執行後 hook
Hooks 怎麼設定?事件、matcher 與指令
Hooks 寫在設定檔的 hooks 鍵底下,結構是「事件 → matcher → 指令」。事件決定什麼時候執行,matcher 篩選工具名稱,指令就是要跑的 shell 命令。
常用的 hook 事件
| 事件 | 什麼時候觸發 | 官方範例的用途 |
|---|---|---|
| SessionStart | 會話開始或恢復時 | 壓縮對話後重新注入專案提醒 |
| UserPromptSubmit | 送出提示、Claude 處理之前 | 每次提示附上目前的分支狀態 |
| PreToolUse | 工具執行之前 | 擋下危險指令、保護特定檔案 |
| PermissionRequest | 要跳出權限確認時 | 自動核准特定的確認 |
| PostToolUse | 工具執行成功之後 | 改檔後跑格式化、記錄指令 |
| Notification | Claude 等你輸入或確認時 | 跳出桌面通知 |
| Stop | Claude 回覆結束時 | 檢查要求的工作是否都完成 |
| SessionEnd | 會話結束時 | 清掉暫存檔 |
資料來源:Claude Code 官方文件〈Automate actions with hooks〉
hooks 寫在哪裡,決定它影響的範圍:
~/.claude/settings.json:你的所有專案,只在你的電腦。.claude/settings.json:單一專案,可以提交到 repo 給團隊共用。.claude/settings.local.json:單一專案,只給你自己。- managed 設定:整個組織,由管理員控管。
- 外掛的
hooks/hooks.json:外掛啟用時生效。 - skill 或子代理的 frontmatter:skill 被叫用後的整個會話,或子代理執行期間。
事件發生時,Claude Code 會把資料用 JSON 傳到腳本的 stdin。Bash 指令放在 tool_input.command 欄位。改檔的路徑放在 tool_input.file_path 欄位。
設定好之後,在 Claude Code 輸入 /hooks,可以看到所有 hook 依事件分組。
注意:command hook 用你的使用者權限執行,能讀寫你帳號碰得到的所有檔案。官方建議加入前先檢查、測試每一條指令。腳本裡的變數要加雙引號,也要檢查路徑裡的
..,並避開.env、.git/與金鑰。
改完檔案怎麼自動跑格式化?
用 PostToolUse 事件,matcher 寫 Edit|Write。Claude 每次改檔或寫檔之後,就會執行你的指令。豪享記帳的前端用 Prettier、後端用 gofmt,所以腳本依副檔名分開處理。
- 在專案建立
.claude/hooks/fmt.sh,內容如下方第一段程式碼。 - macOS 與 Linux 要加執行權限:
chmod +x .claude/hooks/fmt.sh。 - 把下方第二段設定加進專案的
.claude/settings.json。 - 輸入
/hooks,確認 hook 出現在 PostToolUse 底下。 - 請 Claude 改一個
.go檔,打開檔案確認已經排好格式。
#!/bin/bash
# 改檔後依副檔名跑格式化
FILE_PATH=$(jq -r '.tool_input.file_path // empty')
case "$FILE_PATH" in
*.go)
gofmt -w "$FILE_PATH" ;;
*.ts|*.tsx|*.js|*.jsx|*.css)
cd "$CLAUDE_PROJECT_DIR/web"
npx prettier --write "$FILE_PATH" ;;
esac
exit 0
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/fmt.sh"
}
]
}
]
}
}
範例用 jq 解析 JSON,要先安裝。$CLAUDE_PROJECT_DIR 指向專案根目錄,官方建議用它引用腳本。豪享記帳的 Prettier 裝在 web/ 底下,所以先切到那個資料夾。
PostToolUse 有兩個限制要知道:
- 不能復原:工具已經執行完,exit code 2 只會把 stderr 的訊息給 Claude 看。
- 只看得到檔案工具:Claude 用 Bash 指令改的檔案,不會觸發
Edit|Write。可以改用 FileChanged hook,也可以自己加一個 Stop hook,在回合結束時掃一次工作目錄。
Windows 上的 hook 預設用 Git Bash 執行,沒裝 Git Bash 時改用 PowerShell。用 PowerShell 寫的腳本,在 hook 加上 "shell": "powershell"。
危險指令怎麼擋?PreToolUse 與 exit code 2
在 PreToolUse 事件檢查 Claude 要執行的指令,符合危險樣式就用 exit code 2 結束。Claude Code 會擋下這次工具呼叫,並把 stderr 的訊息回給 Claude,讓它換別的做法。
豪享記帳不讓 Claude 強制推送、硬重設 git,也不讓它直接刪資料表。腳本存成 .claude/hooks/guard.sh,加上執行權限後登記到 PreToolUse:
#!/bin/bash
# 執行 Bash 指令前檢查,符合危險樣式就擋下
COMMAND=$(jq -r '.tool_input.command // empty')
PATTERNS=(
"git push --force" "git push -f"
"git reset --hard" "drop table"
)
for pattern in "${PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qi -- "$pattern"; then
echo "已擋下:豪享記帳不讓 Claude 執行「$pattern」" >&2
exit 2
fi
done
exit 0
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guard.sh"
}
]
}
]
}
}
補充:hooks 和 permissions 是同一個設定檔裡並列的鍵。上一節的 PostToolUse 和這裡的 PreToolUse,放在同一個
hooks物件裡。
hook 的結束碼決定接下來發生什麼:
- 0:沒有異議。對 PreToolUse 來說這不等於核准,照一般的權限流程走。
- 2:可以擋的事件會擋下動作,stderr 的訊息依事件回給 Claude 或顯示給你。
- 其他非 0:多數事件視為非阻擋錯誤,動作照常進行。
exit code 2 在各事件的效果
| 事件 | 能不能擋 | exit code 2 時 |
|---|---|---|
| PreToolUse | 能 | 擋下這次工具呼叫 |
| UserPromptSubmit | 能 | 擋下提示,不送給 Claude |
| Stop | 能 | 不讓 Claude 停下,繼續工作 |
| PostToolUse | 不能 | stderr 給 Claude 看,工具已經執行 |
| SessionStart | 不能 | 只把 stderr 顯示給你 |
| PermissionRequest | 不採用 | 要改用 JSON 的 decision 回覆 |
資料來源:Claude Code 官方文件〈Hooks reference〉
PreToolUse 也可以用 exit code 0 搭配 JSON 輸出,把理由一起回給 Claude。官方建議一個 hook 只選一種方式:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "資料表結構要用遷移檔修改"
}
}
寫阻擋用的 hook 要注意什麼?
- hook 壞掉時預設放行:路徑寫錯或腳本當掉,多數事件會照常執行動作。要改成擋下,加上
"onFailure": "block",需要 v2.1.295 以上。 if篩選是盡力而為:"if": "Bash(git *)"可以只在 git 指令時啟動 hook。嚴格的允許或禁止,官方建議用權限規則。- 比對文字擋不住所有寫法:
git -C . push --force就不含「git push --force」這串字。作業系統層級的限制要靠沙箱。 - PowerShell 也要涵蓋:Claude 能用 PowerShell 工具時,也會用它跑指令。matcher 要寫成
Bash|PowerShell。
登記之前,先把範例 JSON 從 stdin 餵給腳本手動測試,最後一行印出的結束碼應該是 2:
INPUT='{"tool_input":{"command":"git push --force"}}'
echo "$INPUT" | .claude/hooks/guard.sh
echo $?
PreToolUse hook 在權限模式檢查之前執行,每一種模式都會跑。回傳 deny 的 hook,連 bypassPermissions 模式都擋得住。反過來,hook 回傳 allow,也不能越過 deny 或 ask 規則。
權限規則怎麼判斷?allow、ask、deny 的順序
權限規則固定依 deny、ask、allow 的順序比對,第一個符合的規則決定結果。規則寫得多具體都不改變順序,所以寬的 deny 會蓋過窄的 allow。
權限規則依 deny、ask、allow 的順序比對,第一個符合的決定結果
豪享記帳的專案設定加上這幾條規則:
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(go test *)",
"WebFetch(domain:nextjs.org)",
"WebFetch(domain:go.dev)"
],
"ask": [
"Bash(git push *)",
"Edit(/api/migrations/**)"
],
"deny": [
"Bash(git push --force *)",
"Bash(curl *)",
"Read(./secrets/**)"
]
}
}
- 強制推送:
git push --force同時符合 ask 和 deny,deny 先比對,所以直接拒絕。 - 資料庫遷移檔:改
api/migrations/前一定要你確認。專案設定裡開頭的/代表專案根目錄。 - curl:官方建議用 deny 擋掉
curl、wget。要抓網頁就改用 WebFetch,再指定允許的網域。
Bash 規則的萬用字元容易寫錯在哪?
*前面的空格算數:Bash(ls *)不包含lsof,Bash(ls*)會包含。- 複合指令每段分開比對:
Bash(npm run *)不會放行npm run test && rm -rf dist。 Bash(*)等於Bash:兩種寫法都代表所有 Bash 指令。
檔案路徑規則怎麼寫?
Read 與 Edit 規則用 gitignore 的寫法,開頭的符號決定從哪裡算起:
| 寫法 | 意思 | 例子 |
|---|---|---|
//路徑 | 從檔案系統根目錄算的絕對路徑 | Read(//home/dev/keys/**) |
~/路徑 | 從家目錄算 | Read(~/.ssh/**) |
/路徑 | 從設定檔的位置算,專案設定就是專案根目錄 | Edit(/api/migrations/**) |
路徑 或 ./路徑 | 從目前目錄算 | Read(./.env) |
資料來源:Claude Code 官方文件〈Configure permissions〉
單一斜線開頭的 /Users/dev/file 不是絕對路徑,絕對路徑要寫兩條斜線。Windows 的路徑會先轉成 POSIX 格式,C:\Users\dev 要寫成 //c/Users/dev。
規則寫在不同設定檔,聽哪一個?
同一個鍵寫在多個檔案時,優先順序由高到低是 managed、命令列、本機、專案、使用者。permissions.allow 這類清單會合併,不會互相覆蓋。任何一層的 deny,其他層都放行不了。
專案設定的 allow 規則,要你在工作區信任對話框按下信任後才生效;deny 與 ask 只會限制動作,所以不受影響。輸入 /permissions,可以看到每條規則來自哪個設定檔。
權限規則擋不住哪些情況?
權限規則比對的是 Claude 寫出的指令文字,不是程式本身。官方列了幾種擋不住的情況:
- 換個路徑呼叫:
Bash(curl *)擋不住/usr/bin/curl開頭的寫法。 - 包在另一個 shell 裡:
sh -c 'curl …'也不會被比對到。 - 程式自己開檔:
Read(./.env)管得到cat這類指令,管不到 Python 腳本自己開檔。
要讓限制不依賴指令文字,就要開沙箱。
沙箱能保護什麼?開啟方式與範圍
沙箱由作業系統限制 Bash 指令能碰的檔案與網路,預設關閉。在會話裡輸入 /sandbox 開啟,或在設定檔把 sandbox.enabled 設成 true。
沙箱支援 macOS、Linux 與 WSL2。原生 Windows 上,指令照常在沙箱外執行;要用沙箱,就把 Claude Code 放在 WSL2 裡跑。
沙箱開啟後的預設範圍
| 項目 | 預設 |
|---|---|
| 寫入 | 工作目錄、使用者暫存目錄,以及你加入的目錄 |
| 讀取 | 大部分的電腦,包含 ~/.ssh 這類憑證檔 |
| 網路 | 經過本機代理,只放行允許的網域,清單一開始是空的 |
| 環境變數 | 沿用 Claude Code 的環境,包含裡面的密鑰 |
資料來源:Claude Code 官方文件〈Configure the sandboxed Bash tool〉
沙箱包住的是 Bash、PowerShell 與 Monitor 執行的指令,以及它們啟動的子程序。下面這些在沙箱外,用你的完整權限執行:
- 內建的檔案與網頁工具:Read、Edit、WebFetch 照權限規則管。沙箱的
denyRead擋不住 Read 工具。 - Claude Code 啟動的其他程式:command hooks、本機的 MCP 伺服器、狀態列指令。
豪享記帳開啟沙箱,擋掉憑證目錄,網路只放行套件來源:
{
"sandbox": {
"enabled": true,
"allowUnsandboxedCommands": false,
"filesystem": {
"denyRead": ["~/.ssh", "~/.aws"]
},
"network": {
"allowedDomains": [
"registry.npmjs.org",
"proxy.golang.org",
"sum.golang.org"
]
}
}
}
allowUnsandboxedCommands 設成 false,官方稱為嚴格沙箱模式。它會關掉「沙箱裡失敗後,改到沙箱外重跑」的退路。沙箱無法啟動時預設會照跑;要改成直接停止,把 sandbox.failIfUnavailable 設成 true。
注意:官方提醒,放行
github.com這類範圍很大的網域,可能變成資料外流的管道。要把 hooks 與 MCP 伺服器也隔離,就把整個 Claude Code 放進容器或虛擬機。
第三方 skill、外掛與 MCP 有什麼風險?
風險分兩種:會在你電腦上執行的程式,以及會進入 Claude 脈絡的指示。官方文件說明,外掛可以用你的使用者權限執行任意程式。skill、指令與代理,則會改變 Claude 怎麼使用手上的工具。
第三方套件會帶進來什麼
| 內容 | 會做的事 | 要注意 |
|---|---|---|
| 外掛的 hooks | 在固定時機執行 shell 指令 | 用你的權限跑,在沙箱外 |
| MCP 伺服器 | 啟動本機程式或連到遠端,提供工具 | 抓外部內容時可能帶進提示注入 |
外掛的 bin/ | 加進 Bash 的 PATH | Claude 的指令可能執行裡面的程式 |
| skill、指令、代理 | 進入 Claude 的脈絡 | 改變 Claude 做事的方式 |
| 自動更新 | 在背景更新外掛 | 審過的檔案可能被換掉 |
資料來源:Claude Code 官方文件的外掛安全與安全頁
幾個官方文件寫明的重點:
- 權限與沙箱管不到外掛自己跑的程式:它們管的是 Claude 的工具呼叫。
- MCP 伺服器沒有經過 Anthropic 的安全稽核:官方建議用自己寫的,或來自信任提供者的伺服器。
- 專案
.mcp.json的伺服器:互動模式會先問你,claude -p與 Agent SDK 會直接載入。 - 專案 skill 的
allowed-tools與 hooks:預先放行的工具不受工作區信任限制。skill 的 hooks 在claude -p也會照用,clone 別人的 repo 要先看.claude/。 - 自動更新:外掛可能在背景自動更新,裝之前先確認它的更新設定。
在別人的 repo 跑 claude -p 要注意什麼?
claude -p 不會跳出信任對話框,會直接把資料夾當成已信任。repo 裡 .claude/settings.json 的 hooks,就會在你的電腦上執行。在不是自己寫的 repo 跑之前,官方建議先挑一種做法:
# 不讀專案的 hooks、skill、子代理、外掛與 .mcp.json
claude --bare -p "列出目錄結構"
# 只關掉這一次的 hooks
claude -p --settings '{"disableAllHooks": true}' "列出目錄結構"
第三方 skill 怎麼用比較安全?先審查、再改寫
我的做法是:找到現成的 skill 或外掛,先不安裝,照四個步驟處理。我審過的幾個裡,有的會自動抓最新版,有的把權杖用明文存起來。我要的是裡面的知識,不是整包程式。
第三方 skill 先唯讀審查,再萃取知識、改寫成自己的版本
- 唯讀審查:看授權、版本與會執行的檔案。外掛看
hooks/hooks.json、.mcp.json、bin/;skill 看腳本與 frontmatter 的allowed-tools、hooks。也確認它會不會自動更新、外傳資料。這一步可以交給子代理,並提醒它把內容當資料看。 - 萃取有出處的知識:留下有來源的做法,標出過時或沒有來源的說法。
- 用自己的話寫成 skill:用官方欄位
name、description,套上自己專案的情境。 - 附上來源與授權:記下 repo、授權與 commit。沒有授權檔的專案,只取觀念、不引用原文。
外掛安裝前,/plugin 的詳細資料會列出它要裝的指令、代理、skill、hooks 與伺服器。clone 下來的外掛,也可以不開會話就列出元件:
claude --plugin-dir ./my-plugin plugin details my-plugin
豪享記帳從一份開源的 Go 審查清單萃取觀念,改寫成自己的 skill。檔案放在 .claude/skills/go-api-review/SKILL.md:
---
name: go-api-review
description: 審查豪享記帳 Go 後端的 API 改動。
when_to_use: 改到 api/ 底下的 handler 時使用。
---
# Go API 審查清單
1. 回給前端的錯誤訊息,不帶出資料庫的原始錯誤。
2. 寫入多張資料表的操作,包在同一個交易裡。
3. 金額用整數存最小單位,不用浮點數。
## 參考來源
| 來源 | 授權 | 版本 | 取用方式 |
|---|---|---|---|
| github.com/example/go-review-guide | MIT | commit 1a2b3c4 | 只取觀念,自己改寫 |
MCP 伺服器怎麼加比較安全?
- 先在 local 範圍試:這是預設範圍,設定存在
~/.claude.json,不進版本控制。 - 權杖用環境變數帶入:
.mcp.json裡寫成${LEDGER_API_KEY},不寫死在檔案裡。 - 不要的專案伺服器直接停用:把名稱加進
disabledMcpjsonServers,任何模式都不會載入。
舊的 hooks 與權限寫法要怎麼改?
我拿官方文件逐項對照自己早期的設定,下面幾處有更好的官方寫法。換成豪享記帳的情境如下:
hooks 與權限:舊寫法與新寫法對照
| 舊寫法 | 問題 | 新寫法 |
|---|---|---|
個人設定只寫 Bash(*),沒有 deny 規則與 hook | 所有 Bash 指令都免問,又沒有擋的機制 | 改成具體的 allow 規則;要全部免問,就搭配擋特定指令的 PreToolUse hook |
| 在原生 Windows 開放所有指令,以為有沙箱把關 | 原生 Windows 不支援沙箱 | 需要沙箱就改在 WSL2 跑,或縮小 allow 範圍 |
設定檔頂層寫 allowedTools,列 Task、LS 這類舊工具名 | 設定檔的權限要寫在 permissions 底下,--allowedTools 是命令列參數;官方工具清單也沒有這些名稱 | 寫進 permissions.allow,工具名照官方工具清單,派子代理的工具叫 Agent |
| 規範寫「改完檔要跑格式化」「每次工具呼叫要留記錄」 | 靠 Claude 記得,沒有程式執行 | 寫成 PostToolUse hook |
唯讀的審查代理設 bypassPermissions | 會跳過所有確認,官方只建議在隔離環境用 | 用 tools 白名單限制成唯讀工具;工作目錄內的讀取本來就免問,CI 改用 dontAsk |
| 第三方 skill 只審授權與程式碼 | 漏看會自動執行與預先放行的部分 | 加看 allowed-tools、frontmatter 的 hooks、.mcp.json、bin/ 與自動更新 |
資料來源:Claude Code 官方文件的權限、沙箱、工具清單與外掛安全頁
常見問題
Q1:Claude Code 的 hook 沒有執行,要怎麼檢查?
輸入 /hooks,確認 hook 出現在正確的事件底下;matcher 要和工具名稱完全一致,大小寫有分。設定檔是嚴格的 JSON,不能有註解或結尾逗號。腳本可以先用範例 JSON 從 stdin 餵進去,手動看結束碼。
Q2:hook 的 exit code 1 會擋下動作嗎?
不會。只有 exit code 2 會擋下,其他非 0 的結束碼是非阻擋錯誤,動作照常進行。想讓 hook 失敗時也擋下,在 command 或 http hook 加上 "onFailure": "block"。
Q3:Windows 可以用 Claude Code 的沙箱嗎?
原生 Windows 不支援,指令會在沙箱外照常執行。要用沙箱,把 Claude Code 裝在 WSL2 的 Linux 環境裡執行。
Q4:權限規則和 hook 的決定衝突時,聽誰的?
hook 用 exit code 2 或回傳 deny,會在權限規則之前擋下,就算有 allow 規則也一樣。反過來,hook 回傳 allow,不能越過 deny 或 ask 規則。
延伸閱讀
- Claude Code Skill怎麼建立?觸發描述與參考檔:把審查後萃取的知識,寫成自己的 skill
- CLAUDE.md要寫什麼?專案規則範例、匯入與自動記憶:哪些規則寫進 CLAUDE.md,哪些要改用 hook
- Claude Code子代理怎麼分工?角色設定、平行與驗收:用 tools 白名單限制子代理能做的事
- 一人開發怎麼用 AI 團隊?:我用 Claude Code 分工與上版審查的實際做法
留言0