出了 bug 的 session,和說「很順利」的 retro
Claude Code 在一個真實的 repo 上發佈了一個編號 bug,而四個經過批准的修改讓這個 bug 再也不可能發佈出去。這次 session 為一個小型請款 API 寫了貸項通知單,測試套件全過,但事後手動執行的一項檢查發現,一個被拒絕的請求燒掉了一個發票號碼。接著我們對那次 session 跑了 retro,逐一批准修正,再把同一個任務重跑一次:這次乾淨地完成了。
retro 是團隊在事故之後召開的檢討,這裡則是對 Claude Code 自己的 session 紀錄來做。我們的第一次 retro 開頭就是 "This session was smooth",而它的發現裡沒有一項是那個 bug。解法是七個步驟,依序是:安裝、執行任務、執行 retro 並告訴它哪裡失敗、追溯每個候選項目、把每個倖存者批准進一個載體、commit 並審核,最後重跑任務。
retro 是什麼,以及安裝 /retro
/retro 是 Matt Pocock 做的一個 skill,它檢討的是一次 coding session,而不是你的程式碼。它會讀取 session 紀錄,也就是 Claude Code 為每個 prompt 和每次工具呼叫保存的檔案,並在七個類別中尋找候選項目,例如「本來可以抓到錯誤的檢查」。它依嚴重程度列出這些候選項目,而且依照它的文件,它只會提出建議:在你挑選候選項目之前,什麼都不會改變。
安裝是第一個步驟:在你的 repo 裡,先更新官方 marketplace,再用專案範圍安裝 skills 套件,讓它只存在於這個 repo。我們的 marketplace 還停在不含 retro 的舊版本,所以要先更新。在新的 session 裡輸入斜線,retro 指令就會出現在清單中。
有一個限制:雲端 session 讀不到你機器上的紀錄,所以要在 session 發生的地方執行 retro。
執行你的任務並讓它跑完:我們的 session 把 bug 發佈出去了
第二個步驟是你每天本來就在做的事:在一般的 session 裡輸入你真正的任務,讓它跑完,並且保持 session 開著,因為 retro 要讀的就是它。
我們用的是一個 Node 和 TypeScript 寫的小型請款 API,有自己的測試套件,沒有任何指示檔,沒有 pipeline,也沒有 commit hook。任務很短,就像你自己會輸入的那樣:為已付款的發票加入依序編號的貸項通知單,並且允許以抵銷剩餘金額的方式作廢一張已付款發票。
這次 session 寫了 route、一份 migration 和它自己的測試,並在停下來之前跑了測試套件。接著我們執行財務部門會手動執行的檢查,也就是事先寫好的隱藏測試,結果有一項需求沒過。
| 訊號 | 結果 |
|---|---|
| 測試套件 | 42 個測試通過 |
| 型別檢查 | 乾淨 |
| 隱藏的財務檢查 | 失敗:拿到的貸項通知單號碼比預期的更後面 |
原因是:handler 在驗證請求之前就從計數器取下一個號碼,而且計數器在被讀取的那一刻就已經儲存。金額太大的貸項通知單被拒絕了,這是對的,但號碼還是消失了,這就不對。這次 session 是從現有的開立發票 route 抄來這個順序的,而那個 route 也以同樣的方式洩漏發票號碼。
我們把這個任務跑了兩次,另一次是乾淨的,所以這是一般 session 有時候會發生的事。
執行 /retro,然後告訴它哪裡失敗
第三個步驟是在同一個 session 裡輸入 retro 指令,然後再執行一次,並在後面寫上失敗的情況。我們的那次開頭是:"This session was smooth."
它列出了幾個項目,沒有一個是那個 bug:缺少護欄、沒人檢查的 migration 規則,以及沒有任何東西讓審查者去執行。retro 讀的是 session 紀錄,而 session 從來不知道自己失敗了。
所以要由你告訴它:把失敗當作參數,也就是指令後面的文字,再執行一次指令。我們寫的是:財務部門測試了這個版本,太大的貸項通知單被拒絕了,下一張有效的通知單卻晚了一個號碼,而且 retro 不應該修改任何東西。
這一次它在自己的程式碼裡找到了原因:它先取號碼,後驗證,並且說它是從現有的開立發票 handler 抄來這個順序的。新的候選項目接著出現:它自己寫的一個測試跑了失敗的流程,卻只檢查狀態碼;一個看起來是純函式、其實不是的計數器函式;還有一條沒人寫下來的慣例。
不論你在 session 之後發現了什麼,都要輸入到參數裡,否則 retro 審核的是一次它以為一切順利的 session。
批准之前,把每個候選項目追溯到一個時刻
第四個步驟是 skill 不會自己做的:要求 retro 引述每個候選項目背後的對話紀錄片段,並刪掉所有它拿不出證據的候選項目。
文件解釋了為什麼光有排序清單還不夠:工作一完成,模型就會忘記 session 中的掙扎,轉而編造一般性的建議。所以,凡是你無法追溯到某個具體時刻的候選項目都要捨棄:一句話,要求 retro 為每一項引述對話紀錄。
在我們的第一次 retro,答案很直接:大多數候選項目在對話紀錄裡都沒有依據。它保留了缺少護欄這一項,因為它自己列出 hooks 資料夾時什麼都沒印出來,並把 migration 檢查當成假設性的而捨棄。
在第二次 retro,也就是知道 bug 的那一次,追溯保留了四個候選項目,捨棄了一個。
| 候選項目 | 追溯之後 |
|---|---|
| 它寫的測試(接受、拒絕、接受,卻從未檢查號碼) | 保留,確定 |
| 沒有任何警告的計數器函式 | 保留,確定 |
| 沒寫下來的慣例 | 保留,較弱 |
| 缺少的護欄 | 保留,不過它表示護欄抓不到這個 bug |
| 最後一個候選項目 | 捨棄:它把同一個時刻算了兩次 |
該 skill 的 repo 上有一個開放中的 issue,統計了多數 retro 執行之後,使用者都會要求它拿出證明。經過追溯的候選項目仍然是模型在讀自己的紀錄,所以標記為較弱的那一列只是一個判斷,由你決定哪些要帶著走。
把每個倖存者批准進它的載體
第五個步驟是整個循環開始見效的地方:一次批准一個倖存者,每個都進入它的載體,也就是這條規則將要存在的地方。skill 給了你一個階梯。機械性的違規得到一個確定性的檢查,沒有例外:linter 裡的一條規則、一個 commit hook,或 pipeline 裡的一個 job。需要判斷的部分則放進程式規範檔,由審查者來讀。你的指示檔,用文件的話說是「極少」使用,只用來指向其他檔案。
我們批准了四個修改:
- 缺少的護欄 變成一個納入版本控制的 commit hook,執行型別檢查和測試,只要其中一個失敗就阻擋 commit。執行環境拒絕把這個檔案設為可執行,所以我們自己執行了那個指令。
- 測試 變成一個鎖住規則的
vitest測試:接受一張貸項通知單、拒絕一張貸項通知單,下一張有效的必須拿到下一個號碼。它第一次跑就失敗了,這正是重點:它抓到了這次 session 發佈出去的 bug。 - 需要判斷的部分 變成新的
CODING_STANDARDS.md裡的一行:只有在所有驗證都通過之後才取序號,讓被拒絕的請求不會在序列中留下缺口。 - 導覽缺口 變成計數器函式上方的一則註解:它會立刻儲存計數器,所以即使呼叫者之後失敗,號碼也已經用掉了,因此只能在驗證之後呼叫它。
結果是四個檔案被修改,CLAUDE.md 裡一行都沒有,因為兩次 retro 都拒絕寫一行進去。第一次說,指向 steering 檔的指標每一輪都會消耗 context 並且會過時;第二次說,把它留作函式上的註解。文件也支持它們:Claude 把這些檔案當作 context,而不是強制執行的設定,要阻擋某個動作,你用的是 hook。
兩個限制:hook 只保護 commit,而註解用 retro 自己的話說,是對下一個呼叫者的賭注,不是已證明的修正。
關上循環:hook 擋下你的 commit、審核、commit、重跑
第六個步驟是 commit,最後一個步驟是重跑同一個任務,你要在這裡看出循環有沒有撐住。
我們把批准的檔案加入暫存區並 commit 時,hook 先執行,並且拒絕了這次 commit。型別檢查通過,接著新的編號測試失敗了,拿到的號碼比預期的更後面。hook 檢查的是磁碟上現有的檔案,而磁碟上仍然是有 bug 的程式碼。
所以回到 session,只要求修正:在驗證通過之後再取號碼,執行檢查,不要 commit。然後再 commit 一次,hook 全綠,你會得到兩個 commit:先是 retro 的修改,再是帶著修正的功能。retro 本身什麼都沒有 commit,這一點在 repo 上有一個開放中的 issue 一字不差地描述過,所以由你來 commit。
接下來執行 /doctor prompt-audit,這是 Claude Code 自己用來檢查你的指示檔有沒有過時內容的功能。這裡有個陷阱:不給路徑的話,它審核的是你的全域檔案,而不是這個 repo。給了路徑之後,它判定新的規範檔是乾淨的,並指出較舊的開立發票 route 違反了這條規則,那是程式碼的 bug,不是 prompt 的問題。
最後一個步驟是用同一個任務開一個全新的 session。我們的那次在寫任何程式碼之前,先讀了程式規範檔、readme、hook 和紅燈的編號測試,然後在它自己的摘要裡寫道:只有在所有這些檢查都通過之後才取號碼,所以被拒絕的請求不會在序列中留下缺口。
| 第一次 session | 循環之後的全新 session | |
|---|---|---|
| 通過的測試 | 42 | 全綠 |
| 隱藏的財務檢查 | 失敗 | 5 項中 5 項通過 |
| 被拒絕的請求之後的號碼 | 被燒掉,有缺口 | 沒有缺口 |
一個誠實的但書:這次全新的 session 拿到了以紅燈測試和規範檔一行的形式呈現的教訓,所以這是循環之後的一次執行,不是基準測試。
結論:什麼時候該跑 retro、什麼時候不該,以及那一條規則
retro 讀的是 session,所以 session 沒看到的東西,要由你寫進參數。這是你最容易踩到的部分。
在一次出錯的 session 之後、在你清掉它之前,或是對舊 session 的紀錄執行 retro。不要讓它自動執行,也不要接到 hook 上:這個 skill 的作者自己說過,你不會想把這件事自動化。文件補充了一個限制:它不會審核你上個月提出的檢查,所以你仍然得自己修剪它們。
要記住的那一條規則就是文件自己的那句話:把你的指示檔當成程式碼,出錯時檢討它,定期修剪,並透過觀察 Claude 的行為是否真的改變,來測試每次修改。這個循環不會讓 agent 變對,它只是讓同樣的錯更快被抓到,而你加進去的檢查就是你現在要維護的程式碼。
這支影片的免費資源包裡有 retro 檢查清單,包含每個步驟、追溯的問題和各個載體,另外還有我們這次執行用的 commit hook 腳本和編號測試,你可以從我們的版本開始。
AIDive