Claude Media
Claude CodeでREADMEのコード例を実行し、古い手順を洗い出す

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ブロックがあります。

  1. node bin/greet.js --name Ada(正しい例)
  2. node bin/greet.js --uppercase --name Ada(フラグが--shoutに変わっている)
  3. require('./src/greet')からgreetを取り出すJS(関数名がhelloに変わっている)
  4. 末尾カンマ付きのJSON設定例
  5. npm run lint(package.jsonにlintスクリプトが無い)
  6. rm -rf dist(破壊的なので実行しない)
  7. 出力例の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 Ada
HELLO, 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種類に分けて読めば、環境が閉じているだけの例を消してしまう取りこぼしは防げます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/hooks
  2. code.claude.com/docs/en/sandboxing
  3. code.claude.com/docs/en/headless
この記事を共有:XはてブLinkedIn