跳到主要內容

【2026 最新】Agent CAPA 教學:把 Claude Code 事故變成測試、Hook 與 CI 閘門

最後更新: ·
Agent CAPA 教學首圖,將 Claude Code 事故轉成測試、Hook 與 CI 閘門

假設 Claude Code 把一個 >= 改成 >,舊測試全綠,卻讓剛好滿額的訂單被收運費。你要做的不是再往規則檔加一句「禁止改壞邊界」,而是建立 Agent CAPA:把這個模擬事故變成同類錯誤會在回歸中失敗、啟用分支政策後會阻擋合併、有人負責,也知道何時能退場的控制。

這篇是可以放進一個 repository 重跑的實作教學。你會從一筆真實形狀的失敗紀錄開始,寫回歸測試、Claude Code Hook 與 GitHub CI,再用原事故與變形案例驗收。讀完後,你不只知道 CAPA 是什麼,也能判斷一條規則該留在文件、lint、測試、Hook,還是 required check。

Agent CAPA 先說結論

Agent CAPA = 事故記錄 + 可執行閘門 + 回歸證據。把它想成軟體團隊的「事故疫苗」:事故記錄說明曾感染什麼,閘門建立免疫反應,回歸證據則證明免疫真的認得同一類變形。

  • 只寫規則:Agent 可能看見,但不是強制控制。
  • 只補測試:能抓 bug,卻可能被同一個 PR 弱化或刪除。
  • 只加 Hook:回饋快,但專案 Hook 仍在開發者工作區內。
  • 完成 CAPA:事故、root cause、owner、控制、驗收與退場條件連成一條可稽核鏈。
Agent CAPA 從事故、ledger、控制層、回歸證據到復查退場的流程圖
Agent CAPA 的閉環:先描述事故,再選可執行控制;沒有回歸證據,就不能結案。圖/AlphaLab

Agent CAPA 是什麼?先分清修正與預防

CAPA 原本是品質系統裡的 Corrective and Preventive Action。ICH Q10 官方指南要求以結構化方式調查 root cause、採取並記錄措施,再評估措施有效性。本文把這套思路移植到 coding agent;這是工程方法的借用,不是把一般軟體專案說成受醫藥法規管制。

Corrective action 是修掉眼前的錯,例如把 > 恢復成 >=Preventive action 是讓同一失敗類別難以再逃逸,例如固定邊界測試、保護測試 ownership,並把它設成合併前必過的 CI。只修程式碼叫修 bug;把學到的東西變成可驗證控制,才是這篇所說的 Agent CAPA。

第 1 步:用最小 ledger 記下事故

不要從「Claude 不准再犯」開始。先在 docs/capa/CAPA-2026-001.json 建一筆 ledger(可追蹤的帳本),至少保留下面十個欄位:

{
  "id": "CAPA-2026-001",
  "symptom": "總額剛好 5000 分仍被收運費",
  "root_cause": ">= 被改成 >;舊測試只有 4900 與 5100",
  "corrective_action": "恢復含等號的比較",
  "preventive_action": "新增邊界回歸、重播 mutant、保護 gate",
  "owner": "reliability-team",
  "evidence": ["tests/regression/CAPA-2026-001.test.mjs"],
  "effectiveness_check": "原案例與變形案例都能殺死 strict mutant",
  "review_on": "2026-12-18",
  "retirement_condition": "產品移除滿額門檻且 owner 批准"
}

Root cause 要描述「為什麼既有系統沒攔到」,不能只寫「Agent 寫錯」。這個案例的可行根因是測試沒有覆蓋等號邊界;如果根因改成 prompt 太模糊、型別允許非法狀態或 review 流程缺口,預防措施也必須跟著換層。

第 2 步:先寫會失敗的回歸證據

先把事故重播成測試,再修程式。原案例驗證 [2000, 3000];變形案例改成 [4999, 1],總額仍是 5000。第二個案例很重要:它證明 gate 學到的是「含等號的邊界」,而不是背下某一組輸入。

test('CAPA-2026-001: exactly 5000 qualifies', () => {
  assert.equal(qualifiesForFreeShipping([2000, 3000]), true);
});

test('CAPA-2026-001 variant: another 5000 qualifies', () => {
  assert.equal(qualifiesForFreeShipping([4999, 1]), true);
});

AlphaLab 在 Node.js 24.15.0 的隔離資料夾實跑:舊測試在 CAPA_MUTANT=strict 下仍是 2/2 通過;新增兩個回歸案例後,strict mutant 變成 0/2 通過;修正版本則是全部 4/4 通過。這裡的 mutant 只是把原錯誤 > 重播,不是宣稱跑過完整 mutation-testing 套件。若要擴大自動突變,可以接著看 Stryker 官方文件與站內的Coding Agent 測試驗證教學

第 3 步:用決策樹選對控制層

不是每個事故都要加 Hook。從「電腦能不能客觀判定」開始問:

  1. 只是偏好或背景知識?放進 CLAUDE.md,並附好/壞例子。若團隊共用 AGENTS.md,可在 CLAUDE.md@AGENTS.md 匯入。
  2. 單檔就能確定違規?交給 formatter、lint、型別或靜態分析,回饋最快。
  3. 要執行行為才知道?寫單元或整合測試;優先固定最小失敗邊界。
  4. 要在 Agent 操作當下提醒或阻止?加同步 PreToolUseStopTaskCompleted Hook。
  5. 會影響合併、部署或安全?同一驗證必須在獨立 CI 重跑,設為 required check,並保留人工批准。

Anthropic 的官方 memory 文件明確把 CLAUDE.md 定位成提供給模型的 context,而非保證嚴格遵守的強制設定。所以文件適合解釋「為什麼」,deterministic gate(同一輸入必得同一結果的閘門)才負責「不能過」。如果你還在整理一般規則與分層,先讀AGENTS.md 五道閘門;這篇則繼續處理事故發生後的完整生命週期。

第 4 步:把 Agent CAPA 接到 Claude Code Hook

第一個 Hook 對 Claude Code 內建 EditWrite 的控制檔編輯提供提前阻擋。Claude Code 的現行 Hooks reference指出,同步 PreToolUse 在工具執行前觸發;command hook 以 exit 2 阻擋並把 stderr 理由回傳。exit 1 通常只是非阻擋錯誤,所以不能寫錯。以下 #!/bin/bash/tmp 範例適用 macOS/Linux;Windows 專案要改用 PowerShell 與對應路徑。

#!/bin/bash
set -uo pipefail
command -v jq >/dev/null || {
  echo "CAPA guard 缺少 jq,拒絕這次編輯" >&2; exit 2;
}
INPUT=$(cat) || { echo "讀不到 hook input" >&2; exit 2; }
FILE_PATH=$(printf '%s' "$INPUT" | jq -er \
  '.tool_input.file_path | select(type == "string" and length > 0)') || {
  echo "無法解析 file_path" >&2; exit 2;
}

case "$FILE_PATH" in
  *"../"*|*"tests/regression/"*|*"docs/capa/"*|*"capa-gate.yml"*)
    echo "CAPA control:請改走人工 review 路徑" >&2
    exit 2
    ;;
esac
exit 0

第二個 Hook 掛在 Stop,在 Claude 準備結束回答時執行 npm run capa:gate。失敗就 exit 2,要求它繼續修;腳本也檢查 stop_hook_active,避免自己形成無限迴圈。因為範例在第二次觸發時放行,它最多強制一次續跑,不是「沒綠就永遠不能停」;官方目前的連續阻擋上限預設為八次,也能用 CLAUDE_CODE_STOP_HOOK_BLOCK_CAP 調高。

#!/bin/bash
set -uo pipefail
command -v jq >/dev/null || { echo "CAPA gate 缺少 jq" >&2; exit 2; }
INPUT=$(cat) || { echo "讀不到 hook input" >&2; exit 2; }
ACTIVE=$(printf '%s' "$INPUT" | jq -er \
  'if has("stop_hook_active") then
     (.stop_hook_active | select(type == "boolean") | tostring)
   else "false" end') || {
  echo "無法解析 stop_hook_active" >&2; exit 2;
}
[ "$ACTIVE" = true ] && exit 0
npm run capa:gate >/tmp/agent-capa-gate.log 2>&1 && exit 0
echo "CAPA gate failed:修正後再結束" >&2
exit 2
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{
        "type": "command",
        "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-capa-files.sh",
        "args": []
      }]
    }],
    "Stop": [{
      "hooks": [{
        "type": "command",
        "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-capa-gate.sh",
        "args": []
      }]
    }]
  }
}

把兩支腳本放在 .claude/hooks/,執行 chmod +x .claude/hooks/*.sh,再把設定存成 .claude/settings.json。先直接餵入一筆應放行與一筆應阻擋的 JSON 測試資料,再開 Claude Code;不要把「設定檔存在」誤當成 Hook 已生效。

實測這兩支腳本時,普通 src/shipping.mjs 編輯回傳 0;碰到受保護回歸檔回傳 2;strict mutant 讓 Stop gate 回傳 2,修正版回傳 0。這是腳本的 deterministic 測試,不是聲稱本機啟動過 Claude Code session。

Hook 的邊界同樣要寫清楚:上面只匹配內建 EditWrite,不會攔住任意 Bash、Python、Node 子程序或符號連結繞路;command hook 也以使用者權限執行,輸入必須驗證。非同步 Hook 不能阻擋;command、HTTP、MCP tool 型的 PreToolUse 逾時會回到一般 permission flow,多數其他事件(含 Stop)逾時則不產生 decision,因此本文這兩個 command hooks 的 timeout 都不是 fail-closed。PostToolUse 發生在工具成功之後,適合回饋,不會撤銷既有副作用。因此本文的架構判斷是:Hook 負責近端回饋,不能取代 sandbox、permissions 與伺服器端合併政策。

第 5 步:用 CI、CODEOWNERS 與人工批准鎖住防線

把相同 gate 放進獨立 workflow,並明確重播事故 mutant。CI 的可觀察結果很簡單:修正版必須綠;故障版本若也綠,代表預防措施沒有辨識力。

name: capa-gate
on: [pull_request]
permissions:
  contents: read
jobs:
  capa-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
      - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
        with: { node-version: 24 }
      - run: npm run capa:gate
      - name: Replay the incident mutant
        run: |
          if CAPA_MUTANT=strict npm run test:regression; then
            echo "CAPA mutant survived" >&2
            exit 1
          fi

接著在 CODEOWNERS 指定 /tests/regression//docs/capa//.claude/、workflow 與 CODEOWNERS 本身的人類 owner。別漏掉 package.json 與 verifier script:上面的 CI 透過 npm run capa:gate 執行,若同一個 PR 能改 script,綠燈就可能失去意義。依照 GitHub protected branches 官方文件,把 capa-gate 設成 required status check、要求 code owner review,並依團隊權限政策決定是否禁止 bypass;全部啟用後,才把「請不要改 gate」變成合併規則。

人工路徑不能消失。需求真的改成「滿 5001 才免運」時,owner 應在另一個 PR 同時更新產品規格、CAPA ledger、回歸測試與 retirement condition;審核者檢查測試在壞版本下確實會失敗。Google 的code review 指南也提醒測試不會測試自己,reviewer 必須檢查測試是否在程式壞掉時真的失敗。

Agent CAPA 回歸驗收卡,顯示舊測試漏攔、回歸測試抓到原事故與變形案例,以及控制檔需要 owner 批准
驗收重點不是「測試有跑」,而是舊防線曾漏掉、新防線能抓到、修正後再恢復全綠。圖/AlphaLab

第 6 步:量測有效性,然後讓控制能退場

每筆 Agent CAPA 至少做四個驗收:

  • 漏攔:原事故與至少一個變形案例,壞版本是否都被抓到?
  • 誤攔:合法改動是否被擋?若有,記錄哪條 matcher 或 assertion 太寬。
  • 維護成本:gate 的執行時間、flaky 次數、owner 工時是否值得?沒有可信數據時先記錄每次 PR 的可觀察結果,不要編造百分比。
  • 到期條件:控制何時再審?只有產品機制消失、替代控制已覆蓋且 owner 批准,才移除。

Ledger 的狀態可用 open → implemented → effective → retired。每次轉態都要有 evidence URL 或 CI run ID;「已提醒 Agent」不是 evidence。retirement_condition 是本文為軟體維護自行加入的欄位,不能觸發自動刪除;ICH Q10 的 CAPA 段落則著重結構化調查 root cause 與評估措施有效性。若想把這種做法擴到整套 Agent 執行環境,可接著讀AI Agent Harness 是什麼動手做 Agent Harness

Agent CAPA 常見的 5 個坑

  1. 把錯的人當 root cause:「Claude 粗心」無法導出可執行控制;改問哪個規格、測試或權限缺口讓錯誤逃逸。
  2. 只重播一筆 fixture:再加一個語意相同、資料形狀不同的案例,避免背答案。
  3. 讓同一個 Agent 改產品、測試與 gate:至少把控制檔交給 CODEOWNERS;高風險專案把 verifier 放在受保護基準分支。
  4. 把 Hook 當安全沙箱:Hook timeout、設定優先序與使用者權限都有邊界;合併政策仍由 CI 與 repository rules 掌管。
  5. CAPA 永不退場:過期 gate 會累積認知與 CI 成本;owner、review date、retirement condition 缺一不可。

截至 2026 年 9 月,Claude Code Hook 要注意什麼?

本文在 2026 年 9 月 18 日重新核對 Anthropic 官方文件。當時 PreToolUseStopTaskCompleted 都有同步阻擋語意,但觸發點不同:Stop 是主 Agent 準備結束一輪回答;TaskCompletedTaskUpdate 把任務標成完成,或 agent-team teammate 還有進行中任務卻要結束時,不是一般回答結尾。兩者都不等於伺服器端 merge gate。專案設定也受 managed、CLI、local、project 與 user scopes 的優先序影響。實作前應用 /hooks 檢視實際載入的 Hook,並直接測試「應放行」與「應阻擋」兩條路徑。

如果公司需要 repository 不能自行換掉的政策,依 Anthropic 官方 settings 文件,可由 managed settings 配置 hooks、permission rules 與 sandbox。不過「放進 managed」不等於所有陣列自動鎖死:hooks 要搭配 allowManagedHooksOnly: true;permission rules 要用 managed deny,必要時搭配 allowManagedPermissionRulesOnly: true;sandbox 的陣列仍可能合併,excludedCommands 也沒有 managed-only 鎖。required CI 因此仍要獨立驗證產品行為。這篇不涉及模型名稱、價格或額度,避免把無關的快速變動資訊混進 CAPA 設計。

Agent CAPA FAQ

1. 每個 Claude Code 錯誤都要開 CAPA 嗎?

不用。優先處理曾逃過現有防線、會重複、影響重大,或顯示系統性缺口的事故。單純拼字且已有 formatter 攔截,不必再造一層流程。

2. 寫進 CLAUDE.md 就夠了嗎?

對偏好可能夠,對硬限制不夠。官方把它定位為 context;必須阻擋的行為應下沉到 lint、測試、同步 Hook、permission 或 CI。

3. Hook 可以取代 CI 嗎?

不能。Hook 適合本機快速回饋;authoritative gate 應在獨立環境重跑,並由 repository 規則決定能否合併。

4. 為什麼阻擋 Hook 要用 exit 2?

對本文這類可阻擋的 command hook,exit 2 是官方 blocking error。PreToolUse 會拒絕工具,Stop 會讓對話續跑;其他事件效果要查事件表。exit 1 或其他非 0/2 code 通常只回報 non-blocking error。

5. 怎麼防止 Agent 刪掉新測試?

不要只靠一句禁止。用 PreToolUse 提前回饋、CODEOWNERS 要求人工 review、required check 重跑,並在 review 中證明測試可抓到故障版本。這不是所有測試的 blanket guarantee:範例 Hook 只涵蓋 EditWrite,真正的合併約束仍取決於受保護的 gate 輸入、CODEOWNERS 與已啟用的 required check。

6. Mutation test 是必需品嗎?

不是。最小版本可只重播原事故;mutation test 是檢查測試辨識力的加強工具,不應搶走 CAPA 的 owner、證據與退場機制。

7. CAPA 何時可以關閉?

控制已實作還不夠。壞版本要被攔、修正版要通過、變形案例要成立,且 evidence 已連回 ledger,才能標成 effective;retired 還需符合預先寫好的退場條件。

8. 小團隊最小可行版本是什麼?

一筆 ledger、兩個回歸案例、一個 required CI check、一位 owner。先閉合一個高價值事故,再決定是否增加 Hook、mutation 或中央 managed policy。

給新手的 6 個重點

  1. 從逃逸事故開始,不從長規則清單開始。
  2. Root cause 要指向可改的系統缺口。
  3. 先證明新測試會讓壞版本失敗。
  4. 文件解釋原因,Hook 提供近端回饋,CI 決定能否合併。
  5. 控制檔交給人類 owner,保留正當變更路徑。
  6. 沒有 evidence 不結案;沒有退場條件不開長期 gate。

接著閱讀

左右滑動查看更多推薦

結語:讓錯誤留下控制,不只留下提醒

下次 Agent 犯錯時,先暫停新增「禁止」句子。挑一個曾逃過測試的事故,用本文的十欄 ledger 記錄它,寫一個原案例與一個變形案例,再把驗證接到 required CI。這時 事故記錄 + 可執行閘門 + 回歸證據才真正閉環。

想繼續系統化學習,可瀏覽 AlphaLab 的AI 專區完整課程。先讓一筆 CAPA 成功退場,再擴大到整個 codebase;可靠性不是規則數量,而是每個控制都能被證明。

ALPHALAB 社群

有問題?來 Telegram 聊

和 Terry、編輯、其他網友一起討論這篇文章。提問、分享觀點,回覆更即時。

加入 Telegram 討論

📩 訂閱 AlphaLab 電子報

每週最多三封:一封 Weekly 週報與最多兩封關鍵 Alpha Signal。

我們不會 spam,隨時可退訂。