Claude CodeでREADMEのコード例を実行し、古い手順を洗い出す
READMEのコードブロックを1つずつ別ディレクトリで実行し、失敗した例だけを直します。7ブロックの小さなREADMEで試した結果と、サンドボックス・hooks・非対話実行の組み合わせ方をまとめます。
READMEのコード例の検証とは、文章中のコードブロックを取り出して実際に実行し、動かなくなった例を見つける作業です。READMEは書いた時点では動いていた例が、コードの改名や仕様変更のあとも残り続けます。目で読み返しても古さには気づきにくいので、機械的に走らせて落ちた箇所だけを直すのが早道です。
READMEの例は、なぜ静かに古くなるのか
コードは変わっても、READMEの例はテストにも型検査にもかかりません。CIが緑のままでも、--uppercaseが--shoutに変わったことをREADMEは知りません。
古くなる経路は主に3つです。
- フラグやサブコマンドの改名
- 関数やエクスポートの形の変更
- 設定ファイル例の書き間違い(JSONの末尾カンマなど)
これらは文章をいくら読んでも判定しにくく、走らせれば一発で分かります。Claude Codeにやらせる価値があるのは、走らせた結果を読んで、READMEを直すかコードを直すかを切り分けるところです。
7ブロックのREADMEを実際に走らせた結果
仕組みを確かめるため、小さなCLIのリポジトリを作り、意図的に古い例を混ぜました。環境はNode.js v20.18.0です。READMEには次の7ブロックがあります。
node bin/greet.js --name Ada(正しい例)node bin/greet.js --uppercase --name Ada(フラグが--shoutに変わっている)require('./src/greet')からgreetを取り出すJS(関数名がhelloに変わっている)- 末尾カンマ付きのJSON設定例
npm run lint(package.jsonにlintスクリプトが無い)rm -rf dist(破壊的なので実行しない)- 出力例のtextブロック(実行対象ではない)
各ブロックを、リポジトリのコピーを作った一時ディレクトリで1つずつ実行しました。結果は次のとおりです。
L5 bash: OK exit=0 Hello, Ada
L11 bash: FAIL exit=2 unknown option: --uppercase
L17 js: FAIL exit=1 TypeError: greet is not a function
L24 json: FAIL exit=1 Expected double-quoted property name in JSON at position 36
L33 bash: FAIL exit=1 npm error Missing script: "lint"
L37 bash: SKIP (destructive)
L43 text: SKIP (not runnable)行頭のLはREADME内の行番号です。7つのうち4つが落ち、2つは意図的に飛ばし、通ったのは1つでした。失敗した4つはいずれも、エラーメッセージから直す場所が分かります。
抽出と実行は小さなスクリプトで足りる
上の結果を出したスクリプトの骨格は次のとおりです。フェンスの言語名で実行方法を分け、破壊的なコマンドは正規表現で飛ばします。
const SKIP = /\brm\s+-rf\b|\bsudo\b|curl[^|]*\|\s*(ba)?sh/;
for (const b of blocks) { // blocks = {lang, start, body}[]
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'rc-'));
fs.cpSync(src, tmp, { recursive: true }); // ブロックごとに作り直す
if (SKIP.test(b.body)) { report(b, 'SKIP'); continue; }
if (b.lang === 'json') { // json は JSON.parse で検査
try { JSON.parse(b.body); report(b, 'OK'); } catch { report(b, 'FAIL'); }
continue;
}
const cmd = { bash: ['bash', ['-e', '-c', b.body]], js: ['node', ['-e', b.body]] }[b.lang];
if (!cmd) { report(b, 'SKIP'); continue; } // text など実行対象外の言語
const r = spawnSync(cmd[0], cmd[1], { cwd: tmp, timeout: 20000 });
report(b, r.status === 0 ? 'OK' : 'FAIL');
}ブロックごとにコピーを作り直すのがポイントです。1つ目のブロックが作ったファイルに2つ目が依存すると、READMEの手順が壊れていても通ってしまいます。
このスクリプトはClaude Codeに書かせても構いません。ただ、スクリプトにしておけば、実行のたびにモデルの判断が入らず、同じ入力から同じ結果が返ります。Claude Codeには、スクリプトの出力を読んで直す役を任せます。
Claude Codeには「実行」と「直す」を分けて渡す
失敗一覧が出たら、Claude Codeに結果を渡してREADMEかコードのどちらを直すかを判断させます。渡し方の例です。
README.md のコードブロックを check-readme.js で実行した結果を貼ります。
FAIL の行ごとに、次のどちらが正しいかをコードを読んで判断してください。
- コードが現在の仕様 → README の例を直す
- README の例が意図した仕様 → コードの側に不足がある(報告だけして直さない)
直したら、同じスクリプトをもう一度実行して FAIL が減ったことを示してください。後半の「報告だけして直さない」が重要です。READMEの例が古いのか、コードの退行なのかは、実行結果だけでは決まりません。--uppercaseのようなフラグは、READMEを直すと決めた時点で利用者向けの案内が変わります。
使うフラグは、--shoutにそろえる直し方が自然です。
node bin/greet.js --shout --name AdaHELLO, ADAこの出力は、手元のリポジトリで直した例を実行した結果です。直したあとに同じスクリプトで再実行し、FAILが0になるまで繰り返します。
実行はサンドボックスの中で行う
READMEには、他人が書いたコマンドが入っています。特にcurl ... | shやrm、sudoを含む例を、本番の認証情報がある手元でそのまま走らせるのは避けます。Claude Codeのサンドボックスは、シェルコマンドの書き込みとネットワークを制限できます。
サンドボックスの既定の挙動は、公式ドキュメントでは次のとおりです。
- サンドボックスは既定でオフ。
/sandboxを実行するか、sandbox.enabledをtrueにして有効にする - 書き込めるのは、作業ディレクトリ、ユーザーごとの一時ディレクトリ、
--add-dirなどで追加したディレクトリ - ネットワークに直接の経路はなく、プロキシが許可ドメインを照合する。許可ドメインは空から始まる
- 読み取りはマシンの大半が対象で、
~/.sshや~/.aws/credentialsのような認証情報のファイルも含まれる
READMEの例がnpm installやpip installを含むなら、許可ドメインを足さない限り、レジストリへの接続でつまずきます。これは「READMEが古い」のではなく「環境が閉じている」ことによる失敗です。後述の切り分けで区別します。
1回きりの検証なら、セッションごとに設定を渡せます。公式ドキュメントの例に沿った形です。
claude --settings '{"sandbox": {"enabled": true, "allowUnsandboxedCommands": false}}'allowUnsandboxedCommandsをfalseにすると、失敗したコマンドをサンドボックスの外で再試行させません。READMEの例がサンドボックスで落ちたら、そのまま「落ちた」として報告させたいので、この設定が向きます。
サンドボックスの外で動くものがある
サンドボックスが囲むのはシェルコマンドです。ドキュメントによると、Read・Editなどの組み込みのファイルツールや、hooksのコマンド、ローカルのMCPサーバーは、サンドボックスの外で動きます。READMEの検証でhooksを使うときは、hooksのスクリプト自体は手元の権限で走る点を覚えておきます。
サンドボックスを含む隔離方式の選び方は、Claude Codeのサンドボックスの選び方で整理しています。
hooksで破壊的なコマンドを止める
サンドボックスに加えて、PreToolUseのhookでrm -rfを含むコマンドを実行前に止めておくと、READMEの例を踏み抜く事故を減らせます。設定の形(matcherとif、jqでの読み取り)はhooksリファレンスのrmを止める例と同じです。止め方は、JSONでdenyを返す代わりにexit 2を使っています。
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
echo "README検証中は rm -rf を実行しません" >&2
exit 2
fi
exit 0設定ファイルでは、BashツールのPreToolUseに結び付けます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(rm *)",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
}
]
}
]
}
}終了コードの意味は決まっています。hookが0で終われば処理は進み、2で終われば、PreToolUseではツール呼び出しが止まります。止めるときの理由は標準エラー出力に書いたメッセージです。2以外の非ゼロでは、止まらずに通る点に注意します。「止めたいのにexit 1にしていた」は、hookの定番の取りこぼしです。
jqが必要になります。公式の例もjqを前提にしているので、入っていない環境では先に入れておきます。
非対話モードで、検証を繰り返し回す
毎回対話で頼むより、claude -pで非対話にしておくと、READMEを直すたびに回せます。
claude -p "check-readme.js を実行し、FAIL したブロックを一覧にして。\
README は直さず、原因の分類だけ報告して" \
--allowedTools "Bash(node check-readme.js),Read"--allowedToolsに載せたツールは、確認なしで使えます。ここではBash全体ではなく、node check-readme.jsだけを許可しています。READMEの検証で、任意のシェルコマンドを無確認にする理由は無いからです。
v2.1.302以降のclaude -pは、許可モードを何も設定しないとき、確認が必要な操作を拒否する方向で始まります。許可していない操作は、通らないまま終わります。READMEの検証を無人で回しても、意図しない操作が通らないということです。
失敗は4種類に分けて読む
FAILが出たら、READMEが古いと決めつけずに、次の4種類に分けて読みます。
| 失敗の型 | 見分け方 | 直し方 |
|---|---|---|
| 例が古い | 見分け方unknown option、is not a function、Missing script | 直し方READMEの例をコードの現状に合わせる |
| 例が断片 | 見分け方x is not defined、前提のimport不足 | 直し方前提の行を足すか、断片と明記する |
| 環境が閉じている | 見分け方名前解決失敗、Operation not permitted | 直し方許可ドメインや書き込み先を足して再実行 |
| 書式の誤り | 見分け方JSONの構文エラー、閉じ忘れ | 直し方その場で直す |
先の7ブロックでは、--uppercaseとgreetとlintが「例が古い」、JSONが「書式の誤り」でした。「環境が閉じている」型は、インストール手順を含むREADMEで最も多く出ます。この型を「古い」と誤認して例を消すと、本当は動く手順を失います。
断片の例は、フェンスの言語名で扱いを分けておくと次回から楽になります。実行させたくない例はtextにし、実行させたい例だけをbashやjsにそろえるルールをREADME側に持たせれば、スクリプトの除外リストが育ちません。例の前提が長いときは、前提の行ごと同じブロックに入れ、単独で走る形にします。
出力例のtextブロックは実行せず、実行結果との比較にだけ使います。サンプル出力が実際の出力と食い違っていれば、それもREADMEの古さの手がかりです。
この検証で分からないこと
実行して通ることは、READMEが正しいことの証明にはなりません。
- 実行しないブロック:
rm -rfやsudoのような例は、飛ばしたまま残ります。読んで確かめるしかありません - 外部サービスが要る例: APIキーや社内レジストリが前提の例は、検証環境では再現できません
- 文章との食い違い: コードブロックが通っても、その前後の説明が古いことはあります
コードブロックの実行は、READMEの検証の入口です。CONTRIBUTING.mdの手順を、cloneし直した環境で通して確かめる方法は、CONTRIBUTING.mdの検証に別にまとめています。READMEそのものを書く段階の話は、documentationスキルでREADMEを書く手順が扱っています。
まとめ
READMEのコード例は、ブロックごとに作り直したディレクトリで実行すると、古い例だけが落ちます。実行はサンドボックスの中で行い、rm -rfのような例はPreToolUseのhookで止めておきます。直す判断はClaude Codeに任せつつ、「コードの側に不足があるときは報告だけ」と指示して、READMEを直すことが案内の変更になる点を人が確かめます。落ちた型を4種類に分けて読めば、環境が閉じているだけの例を消してしまう取りこぼしは防げます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。