AIDive

Claude Codeがバグを出荷。私は4つの修正で二度と起きなくした

AIDive · 公開

コーディングエージェント

バグを出荷したセッションと、「smooth」と言ったretro

Claude Codeは実際のリポジトリで採番のバグを出荷しました。そして、承認した4つの変更によって、そのバグは二度と出荷できなくなりました。このセッションは小さな請求書APIにクレジットノートを追加するもので、テストスイートは緑でしたが、あとから手で実行したチェックで、拒否されたリクエストが請求書番号を消費していることがわかりました。そこでこのセッションにretroを実行し、修正を1つずつ承認して、同じタスクをもう一度走らせると、今度はクリーンに通りました。

retroとは、インシデントのあとにチームが開く振り返りのことです。ここではそれを、Claude Code自身のセッションログに対して行います。最初のretroは「This session was smooth」で始まり、見つかった項目のどれもバグではありませんでした。対策は7つの手順です。インストール、タスクの実行、retroの実行と失敗の伝達、各候補のトレース、生き残った候補を置き場に承認、コミットと監査、そしてタスクの再実行です。

retroとは何か、/retroのインストール

/retroはMatt Pocockが作ったskillで、コードではなくコーディングセッションをレビューします。セッションログ、つまりClaude Codeがすべてのプロンプトとツール呼び出しを記録するファイルを読み、「間違いを捕まえられたはずのチェック」のような7つのカテゴリで候補を探します。候補は重大度順に提示され、ドキュメントによれば提案するだけで、あなたが候補を選ぶまで何も変わりません。

インストールが最初の手順です。リポジトリで公式マーケットプレイスを更新し、スキルパックをプロジェクトスコープでインストールすると、このリポジトリだけに入ります。うちのマーケットプレイスはretroを含まない古いリリースに固定されていたので、先に更新しました。新しいセッションでスラッシュを打つと、retroコマンドが一覧に出ます。

制限が1つあります。クラウドのセッションはあなたのマシン上のログを読めないので、セッションが行われた場所でretroを実行してください。

タスクを走らせて最後まで: うちのセッションはバグを出荷した

2つ目の手順は、毎日やっていることそのものです。本物のタスクを普通のセッションに入力し、終わるまで待って、セッションは開いたままにします。retroが読むのはそのセッションだからです。

使ったのは、Node + TypeScriptの小さな請求書APIで、テストスイートは備えていますが、指示ファイルも、パイプラインも、コミットhookもありません。タスクは自分で打つときのように短くしました。支払い済みの請求書にクレジットノートを追加し、連番で採番すること。そして、支払い済みの請求書は残額をクレジットすることで無効にできるようにすることです。

セッションはルート、マイグレーション、自前のテストを書き、止まる前にスイートを実行しました。そのあと、経理が手で実行するチェック、つまり事前に書いておいた隠しテストを走らせると、要件の1つが失敗しました。

シグナル 結果
テストスイート 42件成功
型チェック クリーン
隠しの経理チェック 失敗: 期待より後ろのクレジットノート番号になった

原因はこうです。ハンドラはリクエストを検証する前にカウンターから次の番号を取り、カウンターは読んだ瞬間に保存されます。大きすぎるクレジットノートは拒否され、それは正しい動作ですが、番号は消えてしまい、それは正しくありません。セッションはこの順序を既存のissueルートからコピーしていて、そのルートも同じように請求書番号を漏らしています。

このタスクは2回走らせ、もう1回はクリーンに通りました。つまり、普通のセッションでもこうなることがある、ということです。

/retroを走らせ、何が失敗したか伝える

3つ目の手順は、同じセッションでretroコマンドを打ち、そのあとに失敗を書き添えてもう一度実行することです。うちの最初の出力は「This session was smooth.」で始まりました。

retroはいくつかの項目に順位を付けましたが、バグを指すものはありません。ガードレールの欠如、誰も確認しないマイグレーションのルール、レビュアーが強制できるものが何もない、という内容です。retroが読むのはセッションログで、セッションは自分が失敗したことを知らなかったのです。

そこで、あなたが伝えます。失敗を引数、つまりコマンドのあとに書くテキストとして、もう一度コマンドを実行します。うちの場合は、経理がビルドをテストしたこと、大きすぎるクレジットノートは拒否されたこと、次の有効なノートの番号が後ろにずれたこと、そしてretroは何も修正しなくてよい、と書きました。

今度は、自分のコードの中に原因を見つけました。番号を先に取り、検証をあとに回していること、そしてその順序を既存の発行ハンドラからコピーしたことです。新しい候補も出ました。失敗する手順を走らせるのにステータスコードしか確認していない自作のテスト、純粋に見えてそうではないカウンター関数、誰も書き残していない慣習です。

セッションのあとで見つけたことは何でも引数に入れてください。そうしないと、retroは問題なく終わったと思い込んでいるセッションを監査することになります。

承認する前に、全候補を場面にトレースする

4つ目の手順は、skillが自分ではやってくれないことです。retroに、各候補の根拠となったトランスクリプトの場面を引用させ、示せない候補はすべて捨てます。

順位付きのリストだけでは足りない理由は、ドキュメントが説明しています。仕事が終わると、モデルはセッション中の苦労を忘れ、一般的なアドバイスをでっち上げます。ですから、特定の場面にたどれない候補は捨ててください。各候補についてトランスクリプトを引用するよう、retroに一文で頼むだけです。

最初のretroの答えははっきりしていました。ほとんどの候補はトランスクリプトに裏付けがなかったのです。ガードレールの欠如は残りました。フックのフォルダを一覧したら何も出なかったからです。マイグレーションのチェックは仮定の話として捨てられました。

2回目のretro、つまりバグを知っているほうでは、トレースの結果、4つの候補が残り、1つが落ちました。

候補 トレース後
自分で書いたテスト(承認、拒否、承認、番号は未確認) 残った(確実)
警告のないカウンター関数 残った(確実)
書かれていない慣習 残った(弱め)
ガードレールの欠如 残った。ただし、ガードレールがあってもこのバグは捕まえられなかったと本人が述べた
最後の候補 落ちた: 同じ場面を二重に数えていた

skillのリポジトリに開いているissueは、retroの実行の大半で、そのあとユーザーが証拠を求めていると数えています。トレースした候補も、モデルが自分のログを読んでいるにすぎません。「弱め」と付いた行は判断であり、先へ持ち越すかどうかを決めるのはあなたです。

生き残りをそれぞれの置き場に承認する

5つ目の手順で、ループの価値が出ます。生き残った候補を1つずつ、それぞれの置き場、つまりルールが住む場所に承認していきます。skillには梯子があります。機械的な違反には、決定的なチェックを置くだけです。リンターのルール、コミットhook、パイプラインのジョブがそれにあたります。判断が要るものは、レビュアーが読むコーディング標準ファイルに入れます。指示ファイルは、ドキュメントの言葉を借りると「incredibly sparingly」、つまりごくわずかにしか使わず、ほかのファイルへのポインタにとどめます。

承認した変更は4つです。

  • ガードレールの欠如は、型チェックとテストを走らせ、どちらかが失敗したらコミットをブロックする、追跡対象のコミットhookになりました。ハーネスがファイルを実行可能にするのを拒否したので、そのコマンドは自分たちで実行しました。
  • テストは、ルールを固定するvitestのテスト1本になりました。クレジットノートの承認、クレジットノートの拒否、そして次の有効なノートが次の番号を持つこと。最初の実行で失敗し、それが狙いでした。セッションが出荷したバグを捕まえたのです。
  • 判断の部分は、新しいCODING_STANDARDS.mdの1行になりました。すべての検証が通ってから連番を取ること。そうすれば、拒否されたリクエストはシーケンスに欠番を残しません。
  • ナビゲーションの穴は、カウンター関数の上のコメント1つになりました。この関数はカウンターを即座に保存するので、呼び出し側があとで失敗しても番号は消費されます。だから検証のあとにだけ呼ぶこと。

変更されたファイルは4つで、CLAUDE.mdへの追記は0行です。2回のretroがどちらも書くことを断ったからです。1回目は、steeringファイルのポインタは毎ターンcontextを消費し、やがて古くなると述べました。2回目は、関数のコメントとして残すよう言いました。ドキュメントも裏付けています。ClaudeはこれらのファイルをCLAUDE.mdと同じくcontextとして扱い、強制される設定としては扱いません。アクションをブロックしたいなら、hookを使います。

制限は2つあります。hookが守るのはコミットだけであること。そしてコメントは、retro自身の言葉では次の呼び出し側への賭けであり、証明された修正ではないことです。

ループを閉じる: hookがコミットをブロック、監査、コミット、再実行

6つ目の手順はコミットで、最後の手順は同じタスクをもう一度走らせることです。そこでループが効いたかどうかがわかります。

承認したファイルをステージしてコミットすると、hookが先に走り、コミットを拒否しました。型チェックは通り、そのあと新しい採番テストが失敗して、期待より後ろの番号を受け取りました。hookはディスク上のファイルをそのままチェックしますが、ディスクにはまだバグのあるコードが残っていたのです。

そこでセッションに戻り、修正だけを頼みます。検証が通ってから番号を取ること、チェックを走らせること、コミットはしないこと。そのうえでもう一度コミットすると、hookは緑になり、コミットは2つできます。retroの変更と、修正入りの機能です。retro自身は何もコミットしません。このことはリポジトリの開いているissueにそのまま書かれているので、コミットはあなたがします。

次に/doctor prompt-auditを走らせます。Claude Code自身による、指示ファイルに古い記述がないかのチェックです。ここに罠があります。パスを渡さないと、リポジトリではなくグローバルのファイルを監査します。パスを渡すと、新しい標準ファイルはクリーンと判定され、古いissueルートがルールに違反しているという指摘が付きました。これはプロンプトの問題ではなく、コードのバグです。

最後の手順は、新しいセッションで同じタスクを走らせることです。うちのセッションは、コードを書く前に、コーディング標準ファイル、readme、hook、そして赤い採番テストを読みました。そして自分の要約に、番号はそれらのチェックがすべて通ってからだけ取るので、拒否されたリクエストがシーケンスに欠番を残さない、と書きました。

最初のセッション ループのあとの新しいセッション
成功したテスト 42 緑
隠しの経理チェック 失敗 5つ中5つ
拒否されたリクエストのあとの番号 消費された、欠番 欠番なし

正直な注意点が1つあります。この新しいセッションは、赤いテストと標準ファイルの1行という形で教訓を渡されていました。ですからこれはループのあとの実行であって、ベンチマークではありません。

結論: retroを走らせる時、走らせない時、1つのルール

retroが読むのはセッションなので、セッションが見ていないことは引数に書いてください。つまずくのはそこです。

retroを走らせるのは、うまくいかなかったセッションのあとで、クリアする前か、古いセッションのログに対してです。自動操縦で走らせたり、hookに組み込んだりしないでください。skillの作者は自分の言葉でこう言っています。これを自動化したくはないはずだ、と。ドキュメントはさらに制限を加えます。先月提案したチェックを監査することはしないので、剪定は引き続き自分でやることになります。

覚えておく1つのルールは、ドキュメント自身の一文です。指示ファイルをコードのように扱い、うまくいかなかったときにレビューし、定期的に剪定し、Claudeの振る舞いが実際に変わるかどうかを観察して変更をテストすること。このループはエージェントを正しくするのではなく、同じ間違いをより速く捕まえられるようにします。そして、追加したチェックは、あなたがこれから保守するコードです。

この動画の無料パックには、すべての手順、トレースの質問、置き場をまとめたretroチェックリストと、うちの実行で使ったコミットhookのスクリプトと採番テストが入っています。うちのものから始められます。

無料リソース

出典

よくある質問

Matt Pocockの/retro skillは何をしますか。
コードではなくClaude Codeのセッションをレビューします。すべてのプロンプトとツール呼び出しのセッションログを読み、7つのカテゴリで候補を探して重大度順に並べます。提案するだけで、あなたが候補を選ぶまで何も変わりません。
なぜretroはセッションをsmoothと言ったのですか。
retroが読むのはセッションログで、うちのセッションは自分が失敗したことを知らなかったからです。42件のテストも型チェックも緑でした。バグを捕まえたのは隠しの経理チェックだけで、最初のretroは「This session was smooth」で始まり、バグを見逃しました。
セッションが気づかなかった失敗を、retroに見つけさせるには。
retroコマンドをもう一度実行し、そのあとに失敗を引数として書きます。うちの場合は、経理が拒否されたクレジットノートと後ろにずれた次の番号を見つけたと書きました。すると今度は、retroが検証の前に番号を取る自分のコードに原因をたどりました。
retroに、その指摘の証拠を出させるには。
各候補の根拠となったトランスクリプトの場面を引用させ、示せない候補は捨てます。うちの最初のretroでは、ほとんどの候補に裏付けがありませんでした。2回目はトレースで4つが残り、1つが落ちました。
ルールはCLAUDE.mdに書くべきですか。
デフォルトでは書きません。うちは4つの変更を承認し、CLAUDE.mdへの追記は0行でした。ドキュメントによれば、ClaudeはCLAUDE.mdを強制される設定ではなくcontextとして扱います。機械的なルールはhookやテストに、判断が要るものはコーディング標準ファイルに置きます。
Claude Codeのセッションにretroを走らせるのは、いつがよいですか。
うまくいかなかったセッションのあとで、クリアする前か、古いセッションのログに対して走らせます。自動化したり、hookに組み込んだりしないでください。作者は、これを自動化したくはないはずだと言っています。

関連動画