那天 AI 決定「清理」
專案沒了
同事的 AI CLI 工具刪了整個專案資料夾,差點無法復原。現在每個人都開全權限跑 AI——沒有護欄的全自動,就是全自動的災難。
那天 AI 刪了資料夾
真實事件,兩位同事,兩個不同的 CLI 工具
兩位同事各自被 AI 刪過東西:一位用 OpenAI Codex CLI,一位用 Google Gemini CLI,兩個工具都很強大,事後才發現都沒設定任何 hook。
第一位同事讓 AI 清理暫存檔,AI 判斷出偏差,直接刪掉整個專案資料夾。
# AI 認為這是「清理」的一部分
# 沒有任何機制攔截這個指令
靠 git history 和 Time Machine 備份,花了不少時間才復原。第二位同事也出了事:AI 整理檔案時誤判路徑,刪掉了不該刪的目錄。
兩次事件的共同點:CLI 工具本身沒有問題,問題是沒有護欄。
AI 是機率性的系統。即使 prompt 寫了「不要刪除重要檔案」,模型仍然可能在某次推理中做出不同的判斷。Hooks 是確定性的——rm -rf / 永遠會被擋下,不管 AI 怎麼想。
沒有人在一個一個 Allow
實際上沒有人一次次按 allow——AI CLI 工具理論上都有權限確認機制,每次執行指令前會問 allow 或 deny,但一天要按幾百次,開發效率直接歸零,所以每個人都開最高權限:
claude --dangerously-skip-permissions
# OpenAI Codex CLI — YOLO 模式,自動批准一切
codex --approval-mode full-auto
# Google Gemini CLI — 同樣的 YOLO 模式
gemini --sandbox=false
這不是少數人的行為,這是常態:開全權限是唯一能讓 AI agent 真正發揮效率的方式,但代價是 AI 可以執行任何指令——刪檔案、force push、寫入系統目錄、推送含密鑰的 commit。沒有護欄的全自動,等於把開發環境交給一個「大部分時候很聰明,偶爾會犯錯」的系統。
問題不是「要不要開全權限」——而是「開了之後,誰來擋住那個偶爾?」
一個 Dispatcher,管理所有 Hooks
v0.1.0 這一版,我們把所有 hooks 整合成一個模組化框架:一個 dispatcher 接收所有事件,依設定檔分派到各個 handler。
Two-Phase Dispatch 是關鍵設計。安全類的 handler(如擋 rm -rf)標記為 critical,永遠第一個跑、不受時間預算限制。其他 handler 共用一個 5 秒預算——超時就跳過,但不會阻塞 AI 的工作流。
擋住 AI 的危險操作
第一位同事誤刪整個專案資料夾那種事故,擋得住的是這三個 handler——它們標記為 critical,無條件優先執行,處理最關鍵的安全問題。
擋 rm -rf /、sudo、git push --force、chmod 777 等危險指令。用 quote-aware 的 shell parser 解析命令,不會被引號或子 shell 繞過。
在 git push 前掃描 diff,攔截 AWS Key、GitHub Token、PEM 私鑰等。只在推送時觸發,不干擾本地開發。支援 # nosec 逐行豁免。
擋 prompt injection 攻擊。偵測「ignore previous instructions」等注入語句、dangerouslyDisableSandbox 等提權操作、curl 外傳資料等竊取行為。三類威脅,獨立掃描。
「當時如果有 bash_safety,第一位同事的資料夾就不會被刪。這是我們第一個寫的 handler,也是整個專案的起點。」
讓 AI 自己檢查自己的作業
commit 之前一定要把驗證跑完,這是品質閘門那四個 handler 立下的規矩。在那之前,驗證跑不跑全看 AI 自己——它寫完程式碼常直接 commit、跳過驗證。
攔截 git commit 和 gh pr create。要求 AI 先跑完驗證(lint、typecheck),通過後寫入一個 marker 檔案,才放行 commit。一次性令牌:用完即消。
Sub-agent 完成任務時,自動跑 lint 和 typecheck。如果不通過,把 agent 打回去修。最多 5 次迴圈、30 分鐘上限——超過就放行,避免卡死。
AI 編輯完檔案後,自動跑 ruff(Python)或 biome(JS/TS)格式化。永遠不會阻擋——即使 formatter crash 也靜默放行。格式統一是好事,但不值得中斷工作流。
Claude Code session 結束時,檢查是否有未 commit 的程式碼變更。預設只發出提醒(不阻擋),需要手動 opt-in 才會真正 block。避免 AI 做完事就走,改動留在 working tree 裡。
讓 AI 不要失憶
一個 sub-agent 生出來就是空白的,主對話的脈絡不會自動跟過去。Context 管理那三個 handler 補的正是這一段——AI Agent 的 context window 本身也有限,記不好等於每次都重新來過。
Sub-agent 啟動時,自動注入專案文件。讀取 .context/ 目錄下的 JSONL 設定檔,每個 agent type 可以有不同的注入內容。上限 8000 字元,避免塞爆 context。
Session 之間的記憶接力。上一個 session 結束前寫好的交接文件,在新 session 啟動時自動注入。5 分鐘 TTL,防止過期資料干擾。
AI 退出 Plan Mode 時,提醒先把計畫存到記憶裡再開始實作。因為 Plan Mode 本身消耗大量 context,不存就浪費了。一次性提醒,不重複。
看見 AI 在做什麼
sub-agent 一多,背景在忙什麼就看不到——這裡靠四個 handler 找回可見度。
強制每個 sub-agent 有描述性名稱。沒取名就擋下來。同時根據 prompt 內容建議合適的 agent type——寫程式碼用 worker,查資料用 explorer,寫文件用 writer。
每個 hook 事件都寫一行 JSONL 到 spool 檔案。延遲 <1ms,不影響任何操作。背景程式每 2 秒批量寫入資料庫,供 dashboard 查詢。整個鏈路零網路延遲。
AI 執行 service restart 或 docker 操作時,通知監控系統進入維護窗口。操作結束後自動解除。讓監控系統知道「這段停機是預期的」,不會誤報警。
AI 做完工作時,用 TTS 語音通知你。不用盯著螢幕等——去泡咖啡,完成了它會叫你。需要你回答問題時也會用語音提示。七層 guard 機制過濾 sub-agent 的中間狀態,只在真正完成時才發聲。
比做了什麼更重要的,是沒做什麼
- 不攔截 Edit / Write AI 寫檔案時不干預。中途打斷 AI 的寫入流程,會造成不完整的檔案狀態。所有審查都延遲到 commit 或 push 時進行。
- 不在閘門裡自動跑測試 verify_completion 只跑 lint 和 typecheck,刻意排除測試。測試太慢、有副作用、可能改變資料庫狀態。放在自動閘門裡,風險比收益大。
- 不自動修復,只阻擋並解釋 bash_safety 不會幫 AI 改寫「更安全的指令」。它只說「這個指令被擋了,因為 X」。讓 AI 自己決定下一步。自動改寫容易引入更詭異的 bug。
- 不做 context budget 評分 我們寫過一個 context_supervisor handler(800 行),嘗試計算 context 使用量並建議壓縮時機。概念很好,但評分模型不準確——context 的「重要性」無法用啟發式規則判斷。已禁用。
- 不 Fail-Closed 所有 handler crash 都放行。有人會問「這不危險嗎?」——更危險的是護欄自己把整個系統卡住。AI 不動了,使用者也不知道為什麼。
hook-observatory
MIT 授權,零外部依賴,裝完即用。
三種安裝方式:Homebrew、Git Clone、或桌面安裝器。
macOS 推薦。兩行搞定,不需要 clone。
brew install hook-observatory
# 安裝到 Claude Code
hook-observatory
所有平台通用。直接跑安裝腳本。
cd hook-observatory
python3 install.py
GUI 引導式安裝,勾選 handler、設定工具路徑。
macOS 首次開啟需執行:xattr -cr 安裝器.app
貼幾行指令,看哪幾行會被擋
bash_safety 那張卡片點名了四類指令:rm -rf、sudo、git push --force、chmod 777。貼幾行你自己跑過的指令,這裡照那四類逐行比對,順便標出哪幾行會撞到 verify_commit 或 secret_scan。全部在你的瀏覽器裡跑,沒有任何東西被送出去,也沒有任何東西被執行。貼幾行就好,不用整份 history——含密鑰、內部主機名、私有路徑的那幾行留著別貼。
常數出處都在本文:四類危險指令與「用 quote-aware 的 shell parser 解析命令,不會被引號或子 shell 繞過」出自 bash_safety 卡片;git commit / gh pr create 要先跑完 lint、typecheck 才放行,出自 verify_commit 卡片;git push 前掃 diff 出自 secret_scan 卡片。這裡做不到的四件事:① 判的是你貼進來的那串字,不是你機器上真的會發生什麼——沒有任何東西被送出去,也沒有任何東西被執行 ② 這是照本文點名的四類做的近似判斷,不是 hook-observatory 的實際規則;本文寫「等危險指令」,代表那份清單本來就不完整,所以「沒被標紅」不等於安全 ③ 本文第三個 critical handler(skill_security)掛在 PreToolUse · Write/Edit,看的是檔案內容不是指令列,這裡查不到 ④ 照本文〈刻意不做〉那條,被擋只會告訴你原因,不會給你一個「比較安全的寫法」。
複製給你的 AI Agent
問題不是「要不要開全權限」,是「開了之後誰擋住那個偶爾」——prompt 寫「別刪重要檔案」沒用,AI 是機率性的,某次推理就翻臉;hook 是確定性的,rm -rf / 永遠擋下。這篇的起點就是第一位同事被 AI 刪掉整個專案資料夾。
把下面貼給你的 AI,讓它先查你是不是在裸奔,再照本文的護欄清單補:
延伸閱讀
Blake Crosley 那篇〈Why Each of My 95 Hooks Exists〉點醒了我:每個 handler 該對應一個真實事故。
| 資源 | 為什麼重要 |
|---|---|
| operonlab/hook-observatory | 本文討論的開源專案 |
| Blake Crosley: Why Each of My 95 Hooks Exists | incident-driven 的 hook 建構思路。每個 hook 背後都有一個事故。 |
| GitButler: Automate Your AI Workflows with Claude Code Hooks | 最早的 hooks 實戰分享之一,啟發了我們的初始設計 |
| aiorg.dev: Claude Code Hooks Complete Guide | 「確定性 vs 機率性」的框架描述,精準表達了 hooks 的價值定位 |
| Jackle: Claude Code Voice Mode | 用 hooks 實現語音通知的實作參考,啟發了 voice_notify handler 的設計 |