この記事は、連載「言わない技術」の第 1 回です。各回はファイルやスクリプトを一つ置けば完結します。連載の全体像と各回の一覧は序論にあります。

今回は、指示ファイルに書いた「必ず実行してください」という行の話です。5 か月前に私が足したその行は、いまも 1 行も減っていません。

前作「読まない技術」の第 6 回で、ルールを、破られたら落ちる自動テストに置き換えると書きました。今回は、その自動テストや hook を置いたに、指示ファイルへ残った行の話です。

まず一つだけ、探してみてください。指示ファイル(CLAUDE.md や AGENTS.md)を開いて、「必ず実行」「最後に確認」「二重チェック」を検索します。何行ありますか。そのうち、その行を消したら、本当に実行されなくなるものは何行でしょう。

CC BY 4.0

頼んだら、手数が倍になって、答えは同じでした

測りました。同じ課題を 2 体の AI に投げて、片方のプロンプトにだけ 1 文足します。

最後に必ず検証の段階を設けてください。抜き出した 10 件を全件、原文と突き合わせて二重チェックし、確認できたことを報告してください。

課題は、ある連載記事 10 本から決まった形の一文を抜き出して表にすること。罠を一つ仕込んであります——10 本目にだけ、その一文が存在しません。作文したかどうかが分かる形です。正解は先に、こちらで機械的に作ってあります。モデルも他の条件も揃えました。

A(検証を指示した)B(指示なし)
正答(正解表と完全一致)10/1010/10
ツール呼び出し8 回4 回
所要時間93 秒43 秒

正答は同じでした。両方とも 10 件が原文と 1 文字も違わず、罠も両方が避けています——10 本目は、どちらも「該当なし」と報告しました。

違ったのは手数です。ツール呼び出しが倍、時間が 2.2 倍。

頼まなかったほうが、自分で照合していました

驚いたのは、こちらです。検証を指示していないほうの報告は、こう始まっていました(原文は英語)。

Sanity check: 目印の語をディレクトリ全体で grep した。9 ファイルにちょうど 1 回ずつ、10 本目には 0 回。

頼んでいません。確かめる作業は消えていませんでした。消えたのは、確かめてくださいという依頼だけです。

Anthropic のプロンプト指針は、この挙動をモデル別のページに書いています——自分の誤りを自分で捕まえて直すので、モデルが既にやることの指示は避けよ、モデル自身の挙動と重なってコストだけ増え、結果は良くならない、と。手元で起きたのは、まさにこれでした。

理解したことは一文にできます。

実行の依頼は、モデルか仕組みのどちらかが既にやっていることの上書きになる。だから指示に残すのは、依頼ではなく理由だけにする。

もう一種類、「仕組みが既にやっている」があります

私の手元の本番プロジェクト——typingtube という、YouTube の音楽動画でタイピング練習ができる Web サービスです——の指示ファイルは 102 行で、「コミット前に必ず実行」と書いてある節が 2 つあります。翻訳ファイルの構文チェックと、デザインのラチェットです。どちらも、pre-commit hook が同じスクリプトを実行しています。頼まなくても通りませんし、頼んでも変わりません。

git の履歴で追うと、私がこの語を足したのは 2 回だけで、どちらも hook を置いたその日でした。仕組みを作ったコミットの中で、同じ内容の依頼も書いている。そして 5 か月、依頼のほうは 1 行も減っていません

仕組み: 実行の依頼に、機構の裏打ちを要求する

置くのは自動テスト 1 本です。指示ファイルが「必ず実行」と書いているコマンドを拾って、pre-commit hook にも実在するかを突き合わせます。

# test/reference/claude_md_enforced_command_test.rb(実物からの簡約版)
class ClaudeMdEnforcedCommandTest < ActiveSupport::TestCase
  test "commands the instruction file asks to run also exist in the commit hook" do
    requested = requested_commands   # 「必ず実行」の直後の囲みから scripts/... を拾う

    # ⚠️ 対象 0 件で緑にしない。抽出が壊れたらここで落ちる
    assert_operator requested.size, :>=, 2, "the extraction found nothing (is it broken?)"

    enforcer = File.read(Rails.root.join(".git-hooks/pre-commit.sample"))
    unenforced = requested.reject { |_lineno, script| enforcer.include?(script) }

    assert_empty unenforced,
                 "a command is asked for in prose but missing from the commit hook. " \
                 "instructions only work when read, so put it in the hook first"
  end
end

見ているのはこれだけです。実行を依頼するなら、先に機構を置け。 機構というのは、hook や自動テストのように、読まれなくても動く仕組みのことです。裏打ちの無い依頼は、読まれたときだけ効きます。

この自動テストは「指示を消せ」とは言っていません。 測る前、私は「機構があるなら節ごと消せる」と思っていました。違いました。同じ節には、こういう行も並んでいます。

YAML 構文エラーがあると Puma が起動不能になる(本番障害に直結)

並走ガードが 1 本しか通さないため

これは残します。機構が持てるのは実行で、機構が持てないのは理由だからです。Claude Code の指示ファイルを点検する公式のコマンドも同じ線を引いていて、モデルが導ける内容は削り、落とし穴・理由・ツール既定と違う慣習は残すと書かれています。だから書き換えは、節を消すのではなく、依頼の 1 行を落として理由の行を残すだけ。行数はほとんど変わりません。

CC BY 4.0 はここまで

頼まなくなったもの

「最後に確認してください」「必ず実行してください」です。確認そのものが減ったわけではありません——モデルは頼まなくても照合しますし、hook は頼まなくても落ちます。やめたのは、その両方に向かって「やってください」と言う行だけです。

CC BY 4.0

注意: 語形しか見ていないこと、測ったのが 1 回であること

死角を二つ、正直に書きます。

一つ、自動テストは「必ず実行」という語形しか見ていません。 「実行しておいてください」に書き換えられたら抜けます。機械に決められるのはそこまでで、言い換えは通ります(前作の第 7 回と同じ構造です)。

二つ、測ったのは 1 課題 1 回です。 そして正直に言うと、トークンは 5% しか変わりませんでした。倍になったのは手数と時間で、トークンではありません。「検証を指示するとコストが跳ね上がる」とは書けません。観測できたのは、質が変わらないまま手数が倍になったことだけです。

検証手順: 両方向から一度ずつ

前提

  • この回の自動テスト(指示ファイルの「必ず実行」と、pre-commit hook の中身を突き合わせるもの)を置き終えていること
  • 指示ファイルに「必ず実行」の行が 1 つ以上あり、その自動テストが今は通っていること

所要時間: 10 分

この自動テストは、2 つのファイルの食い違いを見ています。だから壊し方も 2 つあり、片方だけでは半分しか確かめられません。

手順

  1. あなたが、指示ファイルの側から壊します。「scripts/nonexistent_check.sh をコミット前に必ず実行」の 1 行を足します。⚠️ 実在しないコマンド名にしてください(機構が無いことを確かめる検証です)。自動テストを走らせ、結果を控えます
  2. あなたが、足した 1 行を戻し、今度は hook の側から壊します。pre-commit hook から、指示ファイルが「必ず実行」と書いているコマンドを 1 つコメントアウトします。自動テストを走らせ、結果を控えます

合格条件(すべて満たすこと)

  • 手順 1 で、自動テストが落ちる
  • 手順 1 の失敗のメッセージに、指示ファイルの行番号とコマンド名が出ている(CLAUDE.md:109: scripts/nonexistent_check.sh に当たる形)
  • 手順 2 でも、自動テストが落ちる
  • 手順 2 の失敗のメッセージに、コメントアウトしたコマンド名が出ている

合格しなかったとき

  • 手順 1 で落ちない —— 自動テストが、指示ファイルの「必ず実行」の語形を拾えていません
  • 手順 2 で落ちない —— 突き合わせが片側だけです。hook の側が消えても気づけないので、依頼だけが残った状態を見逃します
  • 落ちたが場所が出ない —— 直す側(AI でも、あなたでも)が、102 行の中から探すことになります

後始末

  • コメントアウトを戻し、自動テストが合格に戻ることと、git diff が空になることを確かめます

止まるべきものが止まるのと、通るべきものが通るのと、両方を一度ずつ。片方だけ確かめた自動テストは、すべてを止める壊れ方を映せません。


CC BY 4.0 はここまで

次回は「具体的な指示を出さない技術」。私は、やり方を書いていません。指示ファイル 102 行に、番号付きの手順は 0 行です。それでも、頼んだ仕事はそのまま返ってきます。

連載「言わない技術」