Claude Codeの「できました」を信じない: 検証レシートで証拠を残す運用
AIの自己申告は鵜呑みにしない。テスト・コマンド出力・スクショで裏取りし、検証レシートとしてPRに残す手順を、コピペで動くコード付きで紹介します。
「修正しました。ビルドも通っています」
Claude Codeがそう報告してきたので、僕はそのままマージしました。翌朝、公開ページを開いたら、購入ボタンのリンク先が古い商品ページのままだったんです。差分はきれいだった。ビルドも本当に通っていた。でも、読者が実際に押すボタンの先は壊れていた。
このとき気づいたのは、僕が確認していたのは「AIの言葉」であって「事実」じゃなかった、ということです。AIは嘘をついたわけじゃありません。ただ、AIが見ている世界(コードの差分)と、僕が守りたい世界(読者の画面とお金の流れ)がズレていただけ。
それ以来、僕はAIの完了報告を信じるのをやめました。代わりに、毎回「証拠(レシート)」を残させるようにしています。今日はその話です。
この記事の要点
- AIの「できました」は感想であって証拠じゃない。テスト・コマンド出力・スクショで裏を取る。
- 裏取りの結果を「検証レシート」という短い定型メモに残し、PRに貼る。あとから見返せる領収書になる。
- レシートには「変更範囲」「触ってはいけない箇所」「証拠コマンド」「公開URL確認」「残るリスク」を書く。感想は書かない。
- HTTP 200は公開確認じゃない。h1や本文の固有語まで文字列で確認する。
- この仕組みを毎回手でやらず、ハーネス(足場)に組み込んで自動化する。
なぜAIの自己申告を信じてはいけないのか
人間の部下なら、「できました」の裏には責任とプライドがあります。間違っていたら気まずいし、評価も下がる。だから自分で見直す。
AIにはそれがありません。Claude Codeは「タスクを完了したと判断した」だけで、「読者の画面で壊れていないか」までは確認していないことがよくあります。理由は単純で、AIが触れる情報と、本番の現実が違うからです。
具体的には、こんなズレが起きます。
| AIが見ているもの | 実際にズレやすい現実 |
|---|---|
| ローカルのコード差分 | デプロイ後の公開URL(キャッシュ・環境変数の差) |
| ビルドが通った事実 | 画像パス・OGP・内部リンクの実体 |
| ボタンの文言を直した事実 | リンク先が古い商品・404・英語ページ |
| 「テスト書きました」の報告 | そのテストが本当に失敗を検知するか |
どれも「嘘」ではなく「確認範囲の外」です。だから対策は、AIを問い詰めることではなく、確認範囲を機械で固定することになります。テストの結果、コマンドの標準出力、スクリーンショット。この3つは感想を挟む余地がない。だから証拠になります。
僕はこれを「自己申告より一次情報」と呼んでいます。AIに「どうだった?」と聞くのではなく、「npm run build の出力を見せて」「公開URLのHTMLにこの文字列が含まれるか確認して」と、観測できる事実を要求する。これだけで事故が目に見えて減りました。
検証レシートとは何か
検証レシートは、AIに任せた1回の変更について、観測した事実だけを短く残す定型メモです。買い物のレシートと同じで、何を・いくらで・いつ買ったかが後から分かる。コードでいえば、何を変え・どのコマンドで確かめ・公開URLで何を見たか、が後から分かります。
ポイントは「感想を書かない」こと。「問題なさそう」は証拠ゼロです。代わりに、こう書きます。
- ❌ 「ビルドは大丈夫そうです」
- ✅ 「
npm run buildが exit 0 で完了。警告0件」 - ❌ 「公開ページも見ました」
- ✅ 「公開URLのHTMLに
検証レシートのテンプレートの文字列が含まれることを確認」
下が、僕がそのままPRコメントやClaude Codeへの最終確認プロンプトに貼り付けている型です。コードフェンスの中なので、コピペしてそのまま使えます。
検証レシート (Verification Receipt)
変更範囲:
- 変更ファイル:
- 読者に見える変化:
- 範囲外(触っていない):
差分の要約:
- 何を変えたか:
- なぜ変えたか:
- 変えてはいけなかった箇所(slug/canonical/CTA/商品リンク/著者/公開日):
証拠コマンド:
- git status --short:
- git diff --stat:
- npm run build(exit code と警告数):
公開URL確認:
- 記事URL:
- 期待するh1:
- canonical:
- fallbackページではない(固有語が含まれる):
収益・CTA確認:
- 無料PDFのCTA:
- 商品リンク(遷移先タイトル):
- 相談リンク:
スクショ確認:
- デスクトップ:
- モバイル:
- CTAが見えているか:
- 文字あふれ・崩れ:
残るリスク:
- リスク1:
- リスク2:
- リスク3:
「範囲外(触っていない)」を1行入れておくと効果が大きいです。Claude Codeは親切心で関係ないファイルまで直そうとすることがあるので、最初に「ここは触るな」と宣言させておくと、暴走の芽を摘めます。
「変えてはいけなかった箇所」も地味に効きます。記事を伸ばす作業に集中していると、slugやcanonical、OGP画像をうっかり書き換えがちです。レシートはその見落としを最後に拾う網になります。
証拠の取り方: テスト・コマンド・スクショ
レシートの各欄を、実際にどう埋めるか。ここが本題です。証拠は「裏取りできる一次情報」でなければ意味がありません。順に見ていきます。
1. 差分とビルドはコマンドの出力で取る
最初の裏取りはターミナルで十分です。差分の量を見て、対象ファイルが意図どおりかを確認し、ビルドを通します。
# 変更ファイルの一覧(意図しないファイルが混ざっていないか)
git status --short
# 差分の規模(巨大な差分は事故のサイン)
git diff --stat
# サイトのビルド(exit code が 0 か、警告が増えていないか)
cd site
npm run build
ここで見るのは「通ったかどうか」だけではありません。差分に身に覚えのないファイルが混ざっていないかを必ず見ます。git status --short に予想外のファイルが並んでいたら、それはAIが範囲外を触った証拠です。レシートの「範囲外」欄と突き合わせます。
2. 公開URLはHTTPステータスではなく中身で取る
ここが一番ハマる落とし穴です。静的サイトでは、存在しないページでもトップページやfallbackのHTMLが返り、HTTP 200になることがあります。つまり200は「ページが存在する証拠」になりません。
だから、h1や本文の固有語が実際にHTMLに含まれるかを文字列で確認します。下のスクリプトはNode.jsの標準fetchだけで動きます(Node 18以降)。
// verify-urls.mjs — 公開URLの中身を文字列で裏取りする
const checks = [
{
url: "https://claudecode-lab.com/blog/claude-code-verification-receipt-workflow/",
// この記事だけが持つ固有語。fallbackページには絶対にない文字列を選ぶ
mustInclude: ["検証レシート", "検証レシートのテンプレート", "残るリスク"],
},
{
url: "https://claudecode-lab.com/training/",
mustInclude: ["相談"],
},
];
let failed = 0;
for (const check of checks) {
const res = await fetch(check.url, { redirect: "follow" });
const html = await res.text();
const missing = check.mustInclude.filter((text) => !html.includes(text));
const ok = res.status === 200 && missing.length === 0;
if (!ok) failed++;
console.log(
check.url,
res.status,
ok ? "OK" : `NG → 不足: ${missing.join(", ") || "(ステータス異常)"}`
);
}
// 1つでも欠けたら異常終了。CIやハーネスから呼ぶときに効く
process.exit(failed === 0 ? 0 : 1);
実行はこれだけです。
node verify-urls.mjs
固有語の選び方にコツがあります。「Claude Code」のような汎用語はトップページにも載っているので、fallbackでも通ってしまう。その記事にしか出てこない語(この記事なら「検証レシートのテンプレート」)を選ぶと、ページの取り違えまで検知できます。
3. 見た目はスクリーンショットで取る
文字列が通っていても、見た目が壊れていることはあります。スマホ幅でCTAボタンが隠れる、翻訳文がカードからはみ出す、コードブロックが横スクロールできない。これは目で見ないと分かりません。Playwrightが入っているプロジェクトなら、デスクトップとモバイルの両方を残します。
// shot.mjs — 公開URLのデスクトップ/モバイルを撮る
import { chromium } from "playwright";
import { mkdir } from "node:fs/promises";
const outDir = "tmp-verification/verification-receipt";
await mkdir(outDir, { recursive: true });
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 1100 } });
await page.goto(
"https://claudecode-lab.com/blog/claude-code-verification-receipt-workflow/",
{ waitUntil: "networkidle" }
);
await page.screenshot({ path: `${outDir}/desktop.png`, fullPage: true });
// モバイル幅(iPhone相当)に切り替えて撮り直す
await page.setViewportSize({ width: 390, height: 1200 });
await page.screenshot({ path: `${outDir}/mobile.png`, fullPage: true });
await browser.close();
console.log(`スクショ保存: ${outDir}`);
撮ったら、上部のh1、最初の3行、中盤のコードブロック、末尾のCTAを見ます。記事が長いほど、冒頭と末尾だけ見て安心しがちなので、意識して真ん中も見ます。画像を残しておくと、レビュー担当や未来の自分が「本当にモバイルで見たのか」を疑わずに済みます。
レシートをPRに残し、ハーネスに組み込む
ここまでで証拠は揃いました。最後の一歩は、それを流れる場所ではなく、残る場所に置くことです。
チャットのやり取りは流れて消えます。だから僕は、埋めたレシートをPRの説明欄かレビューコメントに貼ります。こうすると、レビュアーが同じ前提で会話でき、後日のトラブル対応でも「あのとき何を確認したか」がすぐ分かる。これがレシートを「領収書」たらしめる部分です。
そして、毎回これを手でやるのは続きません。忙しい日に必ずサボるからです。そこで、確認を人の意志ではなく仕組み(ハーネス)に強制させます。ハーネスはAIの外側に置く小さな足場で、「何を確認するか」をコードで固定する役割を持ちます。考え方の全体像はAIに仕事を任せる足場の作り方に、ビルド→検証を自動で回す具体例はビルド→スモークテスト→自動修正を回させる足場にまとめてあります。
最小の組み込み方はシンプルです。上のverify-urls.mjsをpackage.jsonのスクリプトにして、AIの作業の最後に必ず通す。失敗したらprocess.exit(1)で止まるので、「公開URLが壊れたままマージ」が物理的にできなくなります。
{
"scripts": {
"verify": "node verify-urls.mjs",
"ship": "npm run build && npm run verify"
}
}
Claude Codeへの頼み方も、「確認して」ではなく「npm run ship を実行し、出力をレシートの該当欄に貼って」に変えます。すると、AIは感想ではなくコマンドの出力を返すしかなくなる。証拠ベースの会話に自然と切り替わります。権限を広げすぎないための観点は権限監査チェックリストも合わせてどうぞ。
よくある失敗例と避け方
僕や周りが実際にやった失敗を挙げます。
ひとつ目は、HTTP 200を公開確認とみなすこと。fallback HTMLが200で返るサイトでは、これは確認になりません。h1・slug・本文の固有語・canonicalを文字列で見ます。
ふたつ目は、ローカルビルドで満足すること。ビルドが通っても、デプロイが古いキャッシュを返したり、公開先の環境変数だけ違ったり、画像パスだけ壊れることがあります。公開URLの確認は必ず別枠にします。
みっつ目は、CTA確認を「リンクがある」で終えること。リンクが存在することと、読者が次に進めることは別です。ボタン文言・リンク先のタイトル・遷移後の説明まで見ます。冒頭で僕がやらかしたのは、まさにこれでした。
よっつ目は、「テスト書きました」を額面どおり受け取ること。一度わざとコードを壊してテストが赤くなるかを見ます。何をしても緑のままなら、そのテストは証拠になりません。
いつつ目は、Claude Codeに「問題ないか確認して」とだけ頼むこと。確認範囲が曖昧だと、AIは曖昧に「大丈夫です」と返します。レシートの型を渡し、欄ごとに証拠を要求します。
よくある質問
Q. 全部のタスクでレシートを書くんですか? 重くないですか?
A. いいえ。読者の画面やお金に関わる変更(記事公開・CTA変更・LP修正)だけで十分です。タイポ修正に毎回フルレシートは過剰です。代わりにnpm run verifyのような軽い自動チェックを通せば、最低限の裏取りは数秒で終わります。
Q. レシートはどこに保存するのが正解ですか?
A. 流れて消えない場所、つまりPRの説明欄かレビューコメントが基本です。チャット履歴は後から検索しにくいので向きません。スクショだけはtmp-verification/などに置き、PRからリンクします。
Q. Claude Codeに自分でレシートを埋めさせていいですか?
A. 埋めさせていいですが、欄は「感想禁止・コマンド出力を貼る」と指定します。npm run buildやverify-urls.mjsの生の出力を貼らせれば、AIが盛る余地がなくなります。人間は最後にその出力を読むだけにします。
Q. HTTP 200だけで判定してはいけない理由をもう一度。 A. 静的サイトでは存在しないURLでもトップやfallbackのHTMLが200で返ることがあるからです。ステータスは「サーバーが何か返した」証拠であって、「目的のページが存在する」証拠ではありません。本文の固有語まで見て初めて確認になります。
Q. テストとスクショ、どちらを優先すべき?
A. ロジックの正しさはテスト、見た目と導線はスクショ、と役割が違うので両方です。ただ最初の一歩としては、公開URLの文字列チェック(verify-urls.mjs)が費用対効果が一番高いです。
この記事で紹介した内容を実際に試した結果
冒頭のボタン事故のあと、僕はレシートの「収益・CTA確認」欄を埋めるまでマージしない、と決めました。効果は地味ですが確実でした。「ビルドは通ったが公開URLは未確認」「CTAは見たが相談リンクは見ていない」「スクショはデスクトップだけ」——こういう抜けが、埋めようとした瞬間に自分で見えるようになったんです。
そしてverify-urls.mjsをnpm run shipに組み込んでからは、確認を忘れるという事故そのものが消えました。仕組みが代わりに止めてくれるからです。
結局のところ、AIが速いほど、人間がやるべきは「速く書かせること」ではなく「速く証拠を残させること」だと思います。検証レシートは、そのための軽い運用ルールです。まずは次に公開する1本で、CTA確認の3行だけでも埋めてみてください。
もっと型を増やしたい人は教材一覧に確認コマンドやプロンプトのテンプレートを置いています。Claude Code公式の挙動は公式ドキュメントで確認できます。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Codeに1ファイルだけ直させる指示文のつくり方
「もっと良くして」で40行も変えられた失敗から学んだ、触る範囲・検証・戻し方をセットにしたClaude Code用の依頼文テンプレートを紹介します。
Claude Code の権限拒否から復旧する: 止まった理由を次の安全手順に変える
Claude Code のコマンドが拒否されたとき、焦って許可を広げずに、拒否理由、代替手順、証拠コマンド、再試行条件へ分解する方法。
Claude Codeにビルド→スモークテスト→自動修正を回させる足場の作り方
最小スモークテストの選び方、失敗ログを食わせて直させるループ、回数上限と確認ゲートで暴走を止める方法を、コピペで動くコード付きで紹介します。