SKILL.md の決定的処理はシェルに任せる
まとめ
- Claude にファイル探索や有無判定を「自然言語の指示」で任せると、似た名前のファイルを巻き込んで読んでしまう
- 決定的に判定できる処理は
test -fなどのシェルコマンドに移譲し、echoで次にやるべき指示文を直接返す - SKILL.md の手順は「決定的処理(スクリプト)」と「判断・生成(Claude)」に分けて記述すると迷いが減る
起きたこと
Claude Code のスキル room.report を実行したところ、本来は Report.md だけを読むはずなのに、自動生成された Report.auto.md まで読み込んでしまった。SKILL.md には「既存の Report.md があればタイトルを読み取る」と書かれており、Report.auto.md を読めとは書いていない。
原因はシンプルで、Claude が「既存の Report.md があるか」を確認するために ls や Glob で類似ファイルを探した際、prefix が似ている Report.auto.md を「これも Report の一種だろう」と推測して読み込んだ。自然言語の指示は曖昧で、似たものに引っ張られる。
トレードオフ
| 観点 | Claude に探索させる | スクリプトで判定する |
|---|---|---|
| トークン消費 | 推論と読んだ内容が context に乗る | 固定の短い出力のみ |
| ツール呼び出し回数 | 候補が複数あれば Read を重ねる | 1 回 |
| 正確性 | 表現の揺れで誤判断する | 決定的 |
| 再現性 | プロンプトと文脈でブレる | 同じ入力で同じ結果 |
| 適性 | 文脈解釈・自然言語生成 | 有無判定・パターンマッチ |
決定的に答えが決まる処理ほどスクリプトに分がある。Claude に判定させると、毎回 context 汚染と誤読込のリスクを背負うことになる。
やった修正
SKILL.md の手順を「Report.md があるか確認して、あれば読む」という散文から、次のコマンドに置き換えた。
test -f Report.md \
&& echo "Report.md を Read し、frontmatter 直後の1行目タイトル (# ...) を使用する" \
|| echo "会話内容から自然なタイトルを付ける(仮名ディレクトリ YYYY-MM-DD_HHMMSS をそのままタイトルにしない)"ポイントは、echo の中身を判定結果(exists / none)ではなく、 次にやるべき指示そのもの にしたこと。Claude はシェル出力をそのまま読んで実行すればよく、「判定 → 対応のマッピング」を介さない。
設計指針
SKILL.md のフローを書くときは、各ステップを 2 種類に分類する。
- 決定的処理 — スクリプト化して
echoで次の指示を返す。シェルが「指示書ベルトコンベア」になる - 判断・生成 — テンプレートと制約だけ提示し、自然言語で Claude に任せる
両者を混ぜて散文で書くと、Claude が決定的処理まで推論で解こうとして迷う。手順 1 を機械化しておけば、推論コストは判断と生成に集中投下できる。
似た構造のスキルを書くたびに、「これは決まった答えがある処理か、それとも文脈次第の判断か」を意識的に切り分けると、スキルの再現性と省トークン性が両立しやすい。