我:「重構這個模組的權限檢查邏輯」
Claude Code 直接動手,把權限檢查全抽成 middleware,改了 8 個檔案。但我其實只想把重複的部分抽出來、保留 inline 的快速檢查。重做 30 分鐘。
它沒問我範圍,我也忘了講。這不是技術錯誤,是「沒問清楚就動手」的方向錯誤——而方向錯誤的代價遠大於技術 bug。
我寫了一個 UserPromptSubmit Hook:偵測到複雜任務時,自動注入「先回答四個問題」的提示,強迫 Claude Code 在動手前先確認方向。這篇拆解它的設計。
TL;DR
| 項目 | 設計 |
|---|---|
| Hook 類型 | UserPromptSubmit(無 matcher,所有訊息觸發) |
| 語言 | Python,~160 行 |
| 判斷 | 27 條 regex(17 簡單 + 10 複雜)+ 訊息長度 |
| 三段邏輯 | 先判簡單 → 再判複雜 → 不確定看長度(偏觸發) |
| 提問 | Why / What / How / Edge cases 四個 |
| 觸發方式 | approve + message 注入(不阻擋送出) |
| 誤判處理 | 結尾寫「若是簡單任務忽略此提示」給 AI 台階 |
為什麼 Claude Code 不會自己問
Claude Code 的訓練目標是「有用」——直接動手解決比反覆確認更「有用」,這在簡單任務是對的。但複雜任務的失敗成本高:
| 任務 | 失敗成本 |
|---|---|
| 改 typo / 加 log | 一行回滾 |
| 重構 / 優化 | 30+ 分鐘重做 |
| 新功能 | 漏掉模組、上線後客戶回報 |
我踩過的四種方向錯,根因都是「沒講清楚 + 沒問清楚」:
- 重構權限檢查:沒講範圍(What)→ 它全抽成 middleware,但我只要抽重複的
- 優化查詢:沒講目的(Why)→ 它加了 Redis cache + 改 schema,但我只想看
EXPLAIN - 加欄位:沒講範圍精確版 → 它改了表單/列印/匯出,漏了 API 上傳等 4 個模組
- 整合表單:沒講做法(How)→ 它一次性統一三個表單,但歷史資料相容性壞了
「簡單任務直接做、複雜任務先問」這個偏好,需要一個外部訊號告訴 Claude Code。Hook 就是那個訊號。
UserPromptSubmit:注入 context 的最佳時機
使用者送出訊息
│
▼
[UserPromptSubmit Hook] ← 本文主角
│ ├─ 可注入 message 到 context
│ └─ 可阻擋訊息送出
▼
Claude Code 看到使用者訊息(+ hook 注入的 message)→ 推理 → 動作
它在使用者按 Enter、Claude Code 開始處理之前觸發——可以根據訊息內容動態修改 Claude Code 的行為。
Hook 的輸入輸出
// Input
{ "prompt": "重構這個模組的權限邏輯" }
// Output
{ "decision": "approve", "message": "💡 偵測到複雜任務..." }
我用 approve + message:不阻擋使用者(不像 block 要重打訊息),但在 Claude Code 看到使用者訊息之前先讓它看到我的提示。誤判也沒關係——頂多它多讀一段文字。
三段判斷的決策邏輯
prompt 進來
│
├─ is_simple_task()? ──Yes──→ 不觸發(「輸入太短」/「符合簡單模式」)
│
└─ No → is_complex_task()? ──Yes──→ 觸發(「符合複雜模式」)
│
└─ No(不確定)→ len > 30 ? 觸發 : 不觸發
順序很重要。「重構這個變數命名」這句命中「重構」(複雜),但其實是小改動——如果先檢查複雜模式會誤判。所以先檢查簡單模式:
SIMPLE_TASK_PATTERNS = [r"改.{0,3}(變數名|函數名|檔名)", ...] # 命中「改變數命名」,優先放行
簡單模式的優先級高於複雜模式,這個順序消除大量誤判。不確定時(中等長度、沒明確關鍵字)偏向觸發——問四個問題的成本遠低於做錯方向。
Pattern 設計
簡單任務(17 條)
SIMPLE_TASK_PATTERNS = [
r"(改|修|換).{0,5}(typo|錯字|拼錯|顏色|文字|標題)", # 小修改
r"加.{0,3}(log|console|print|註解|comment)",
r"^(怎麼|如何|為什麼|什麼是|哪裡|哪個|是否|有沒有)", # 詢問類
r"^(看|查|找|搜尋|列出|顯示)", # 查看類
r"^(執行|跑|run|test|build|commit)", # 簡單指令
r"^git\s+(status|log|diff|branch)",
r"^(ok|好|確認|是|對|可以|繼續|next|yes|y)$", # 確認/繼續
# ... 共 17 條
]
邏輯:訊息短、以疑問詞開頭、明確說「改 typo / 加 log」、或是確認回應 → 簡單。
複雜任務(10 條)
COMPLEX_TASK_PATTERNS = [
r"(重構|refactor|整合|migrate|遷移)", # 大型改動
r"(優化|改善|提升).{0,5}(效能|performance|速度)",
r"(所有|全部|每個|整個).{0,5}(檔案|檔|模組|頁面)", # 多檔案
r"(批次|批量|大量)",
r"(架構|設計|規劃|方案)", # 架構層級
r"(資料庫|db|schema|table).{0,5}(設計|變更|改|新增)",
r"(不確定|可能|或者|也許|應該)", # 不確定性
r"(怎麼做比較好|你覺得|建議)",
# ... 共 10 條
]
邏輯:「重構/優化/整合」、「所有/整個/批次」、「不確定/可能/應該」→ 複雜。
Regex 設計的三個陷阱
陷阱一:過廣的 OR
r"(改|修|換)" # ❌ 命中「我改了密碼後忘了 commit」
r"(改|修|換).{0,5}(typo|錯字|拼錯|顏色|文字)" # ✅ 用 {0,5} 限制 context 距離
陷阱二:忘記錨點
r"(ok|好|確認)" # ❌ 命中「請確認 ok 後執行重構」
r"^(ok|好|確認)$" # ✅ 整句就是 ok / 好 / 確認
陷阱三:中英混用
r"(重構|refactor|整合|migrate|遷移)" # 中英都要涵蓋(「幫我 refactor 這段」很常見)
為什麼只有 27 條
多 pattern 不等於更準:太多會互相干擾、誤判機率上升、維護成本高。從少開始、用 log 校正、慢慢加(見後面校正章節)。
短訊息預設不觸發
if len(prompt) < 15:
return True, "輸入太短" # 視為簡單
我統計過自己半年的 prompt 長度:確認/指令多在 15 字內、真正的複雜任務幾乎都 > 30 字。< 15 字幾乎沒有複雜任務,這個閾值過濾掉大部分噪音。
Interview Mode 的四個問題
判定為複雜任務時,注入這段:
💡 偵測到複雜任務({reason}),建議使用 Interview Mode
🎤 請先回答(可簡答,我會追問細節):
1. Why - 為什麼要做?想解決什麼問題?
2. What - 具體做什麼?範圍包含哪些?
3. How - 有偏好的做法或技術限制嗎?
4. Edge cases - 有需要特別處理的例外情況嗎?
💡 如果這是簡單任務,忽略此提示直接說明需求即可。
四個問題正好對應我四次踩坑的根因:
| 問題 | 防止 | 對應踩坑 |
|---|---|---|
| Why | 「我以為你要 X,結果你要 Y」 | 優化查詢(其實只想看 EXPLAIN) |
| What | 範圍誤判(1 個檔案還是 50 個) | 重構權限(全抽 vs 抽重複)、加欄位漏模組 |
| How | 技術選擇踩到「不要碰」的雷 | 整合表單該分階段 |
| Edge cases | 邊緣情況的 bug | 通用 |
少於四個容易漏,多於四個會煩。
「可簡答」:複雜任務不該變成填表單,每題兩句話就夠——重點是「有想過」。結尾「忽略此提示」:誤判難免,給 Claude Code 一個明確台階,否則它會努力把「改個變數名」也套 Interview 流程。
效能
純函式,只看 prompt 字串、不依賴外部狀態:
| 操作 | 時間 |
|---|---|
| Python 啟動 | 30-50ms |
| 27 條 regex | < 15ms |
| 總計 | 30-65ms / 次提示 |
比 safety_guard hook 快——後者要跑 git rev-parse 拿分支名 + 讀寫 state.json。這個 Hook 沒有狀態,所以更快。
Edge Cases
| 情境 | 行為 |
|---|---|
| 「幫我 refactor 這段」 | 命中複雜模式(中英混用已涵蓋) |
| 「改個變數名稱」 | 簡單模式優先,不觸發 |
| 「重構這個變數命名」 | 命中「重構」→ 觸發,但有「忽略提示」台階 |
git status | 命中簡單模式,不觸發 |
| 「優化縮排」 | 因「優化」加了 `.{0,5}(效能 |
誤判成本 = Claude Code 多讀一段提示,零實質影響。漏接(複雜沒觸發)的成本才高,所以保守策略偏向誤判。
替代方案比較
| 方案 | 缺點 |
|---|---|
| 不處理 | 持續踩方向錯誤的坑 |
| CLAUDE.md 寫規則 | Claude Code 不一定遵守、複雜任務還是直接動手 |
Slash Command /interview | 要使用者主動下指令、會忘記用 |
| regex Hook(本文) | 會誤判、要寫程式——但自動觸發、無感、可調 |
| LLM 判斷複雜度 | 準但慢又貴(每個 prompt 一次 API 呼叫) |
準確性不是 Hook 的最高目標——速度才是。regex 的成本是偶爾誤判,而誤判成本接近零。實務上我組合用:Hook 自動觸發 + /interview 手動 + CLAUDE.md 通則備援。
除錯與測試
Hook 沒觸發時的檢查:.claude/settings.json 路徑是絕對路徑嗎?Python 在 PATH 嗎?注意 UserPromptSubmit 不需要 matcher。
獨立測試(不用起 Claude Code):
echo '{"prompt": "git status"}' | python detect_complex_task.py # 不該注入 message
echo '{"prompt": "重構整個權限模組"}' | python detect_complex_task.py # 該注入 message
可以寫成 pytest 集合,把「短訊息不觸發」「重構觸發」「優化縮排不誤觸」變成回歸測試——尤其調 regex 後跑一遍確認沒影響其他 case。
漸進式演化:從 30 行開始
第一版只做兩件事——判斷長度、注入提示:
import sys, json
INTERVIEW = """💡 偵測到較複雜的任務,請先回答:
1. Why 為什麼做? 2. What 做什麼? 3. How 有限制嗎? 4. Edge cases 例外?"""
def main():
data = json.loads(sys.stdin.read())
prompt = data.get("prompt", "")
if len(prompt) > 30:
print(json.dumps({"decision": "approve", "message": INTERVIEW}))
else:
print(json.dumps({"decision": "approve"}))
main()
跑兩天看誤判/漏接,然後逐步加:
| 階段 | 加什麼 |
|---|---|
| Week 1 | 長度 > 30 觸發 |
| Week 2 | 明確的簡單模式(過濾 git status 等) |
| Week 3 | 明確的複雜模式(重構/優化必觸發) |
| Week 4 | 「忽略此提示」台階 |
| Week 5+ | 依踩坑經驗加 pattern |
規則是長出來的,不是設計出來的。
Pattern 校正:從 log 反推
每週看一次 log,逐筆判斷「觸發 / 沒觸發」是否正確。真實案例:
prompt: "優化這段 SQL 的縮排"
trigger: True (命中複雜模式「優化」)
「優化縮排」是格式整理,不是效能優化——誤判。修法是給「優化」加 context:
# Before: r"(優化|改善|提升)"
# After: r"(優化|改善|提升).{0,5}(效能|performance|速度)"
只有「優化效能」「改善速度」才觸發,「優化縮排」不會。
另一個案例「批次提交剛改的檔案」也誤觸(命中「批次」),但我選擇不修——誤判成本(多讀一段提示)< 修 pattern 的維護成本。判斷要不要修,比修本身更重要。
為什麼這個 Hook 比想像中重要
我原本以為 Claude Code 的「失敗」是技術錯誤——壞 SQL、空指標、邏輯錯。
用久了才發現,最大的失敗是「方向錯」:我要 A 它做 B、我以為它會問它直接做、我預期改 100 行它改了 500 行。這些不是 bug,是期望落差,根因是「沒問清楚就動手」。
Interview Mode Hook 不解決技術問題——它解決溝通問題。把「直接動手」變成「先問清楚再動手」,就避免了大部分方向錯誤。
結語
Claude Code 的速度是最大優勢,也是最大陷阱——速度太快,做錯方向時你來不及攔。這個 Hook 強制它慢下來:代價是複雜任務多花 1-2 分鐘問問題,回報是避免做錯方向的 30 分鐘重做。
如果你常用 Claude Code 處理複雜任務,從 30 行的最小版本開始,每週看 log 調 pattern。重點不是這 160 行 Python,是讓 AI 配合你的工作節奏。
這個 Hook 跟攔截危險指令的 PreToolUse Hook 搭配——一個攔方向錯誤、一個攔危險操作,是我目前 Claude Code 體驗最大的兩個優化。