AI

SKILL.md に必須の参照ファイルを注入する

  • AI
  • Claude Code
  • スキル
  • 動的コンテキスト注入
  • 参照ファイル

スキルの参照ファイルを毎回必ず届けたいなら、「読むこと」と書く代わりに、SKILL.md の本文で cat の出力を注入します。

```!
cat "${CLAUDE_SKILL_DIR}/references/_core-rules.md" 2>/dev/null || echo "(reference missing: _core-rules.md)"
```

切り替えの根拠は2つです。公式が読み込みを裁量だと書いていることと、注入された内容は飛ばしようがないことです。

読めと書いても、参照の中身は付いてこない

前の2本で、規約の記述が足元の挙動に暗黙に依存していること、依存先を数え直したら3つ見つかったことを書きました。3つ目が参照ファイルです。SKILL.md に「このファイルを読むこと」と明示していますが、呼び出して測ると、Skillツールが返すのは SKILL.md だけでした。参照の中身はそこに入っていません。

ブログ規約は、規約について嘘をつくエージェントに読ませる規約が、実際の挙動と食い違ったまま残っていました。参照ファイルを読めと明示的に書いた指示が、その通りには動いていなかった件を起点に、規約そのものを検査した記録です。 ブログ削れと言われたのは、道しるべではなかった「規定的な作り込みを削れ」という助言は、禁止形や思考手順のことを指しています。ヒントやフックやワークフローまで削れと読むと、裁量を渡したのに黙るエージェントが残ります。制約と整備を分けた上で、依存していた挙動を数え直した記録です。

1本目は一覧を作ると書いて終わり、2本目はその走査を週次の運用に乗せるところまで書きましたが、何を拾うかまでは決めていません。この記事は、その一覧に載るはずの1行、つまり参照ファイルについて何をしたかの記録です。

公式ドキュメントも、参照は読まれない前提で書かれている

まず調べたのは、読まれることを期待していい根拠が公式にあるかどうかでした。ありませんでした。

Skill authoring best practices は、スキルの読み込みをこう説明しています(以下、訳は筆者)。

Claude が SKILL.md を読むのはそのスキルが関係するようになったときだけで、追加のファイルを読むのは必要になったときだけである

裁量だと書いてあります。同じページは、入れ子になった参照だと head -100 のようなコマンドで先読みして情報が欠けること、参照は SKILL.md から1階層に留めることも書いています。さらに、スキルの改善で観察すべき項目として「辿られなかった参照」を挙げ、リンクをもっと目立つように書き直す必要があるかもしれない、と続けます。読まれないのは不具合ではなく、書き手が直すものだという扱いです。

Claude Code のスキルのドキュメントには、もっと直接的な一文があります。

スキルが最初の応答のあと挙動に影響しなくなったように見える場合、内容はたいてい今もそこにあり、モデルが別のツールややり方を選んでいる。description と指示を強めるか、hooks を使って挙動を決定的に強制する

指示で効かないなら、必ず実行される仕組みに移せ、と公式が書いています。参照ファイルはまさにその場面でした。

書いた指示が通らないのは、参照ファイルに限らない

書いた指示がそのとおりに通らないという報告は、珍しくありません。「参照を読め」と書いたのに読まれない、という症状そのものは出てきませんが、もう少し広く探すとすぐ見つかります。

  • #47598 — スキルが自動で読み込まれず、カスタムサブエージェントへの委譲も起きなくなった。frontmatter にトリガーを書き、CLAUDE.md に委譲表まで置いた状態での報告
  • #27949/clear のあと、スキルの最初の指示が効く会話と効かない会話がある
  • #72531 — コンテキストの要約後、セッション開始時に読ませたはずの指示が守られない

3件ともクローズ済みで、いま同じことが起きるとは限りません。それでも、読ませたいものを指示で届ける限り、条件によって通ったり通らなかったりする、という形は共通しています。

anthropics/skills#1220 は、そこを4構成で測った対照実験です。3つの参照ファイルに合言葉を仕込み、Read を使わせずに呼び出したところ、description に事前読み込みのヒントを書く構成も、frontmatter に preload フィールドを足す構成も効かず、内容を SKILL.md に埋め込んだ2構成だけが合言葉を全部返しました。ハーネスが何も先読みしないという設計どおりの結果です。

こちらの結論も同じところに落ちます。読まれるかどうかが条件次第で変わる以上、毎回必要な内容を指示で届ける理由がありません。

以前の書き方は、SKILL.md に参照を読めと書くもので、読むかどうかはモデルが決めるため、読まれるとは限らず、読んだかどうかも分からない。今の書き方は、本文で cat を注入するもので、ハーネスがモデルに渡す前にコマンドを実行して中身に置き換えるため、参照の内容を飛ばしようがない

プラグインで配っても、${CLAUDE_SKILL_DIR} は展開される

切り替える前に、確かめておくことが1つありました。${CLAUDE_SKILL_DIR} がプラグイン由来のスキルでは展開されないという Issue が 2.1.220 で報告されたまま残っていて、ラビーのスキルはプラグインとして配っているからです。ここが動かないなら、この方式は最初から採れません。

結論から書くと、2.1.233 では展開されました。捨てる前提のプラグインを作り、参照ファイルに MARKER-ZEBRA-7731 という目印だけを置いて --plugin-dir で読み込んだところ、応答にその目印がそのまま出てきます。推測できる文字列ではないので、参照の中身が本文に届いたと言えます。

書き方そのものは、コミットメッセージ生成のスキルで先に使っていたものです。

ブログClaude Codeのコミットメッセージ生成が遅いのでツール呼び出しをゼロにしたClaude Codeスキルの動的コンテキスト注入でgit diffを事前に本文へ埋め込み、サブエージェントの推論パスを3回から1回に減らした手順を紹介します。

同時に、落とし穴を2つ踏みました。どちらも黙って失敗します。

1つ目は ! の位置です。DIR=!`echo …` と書いた行は展開されず、文字列のまま残りました。インライン形式が認識されるのは ! が行頭か空白の直後にあるときだけで、6月の記事に自分で書いておいた制限です。ただし ${CLAUDE_SKILL_DIR} のほうは単なる文字列置換なので、実行されなかったコマンドの中でも実パスに変わっていました。動いたように見える出力が返ってくるぶん、気づきにくい形です。

2つ目は前処理そのものの失敗です。サンドボックス内のシェルから起動した1回目は、! の前処理が落ちたのに claude -p が成功として返りました。応答は空、num_turns は 0 です。中断すること自体はドキュメントに書かれています。

失敗したコマンドは、そのプレースホルダーだけでなくスキルの呼び出し全体を中断させる。その呼び出しについて、Claude はスキルの内容を一切見ない

書かれていないのは、それが成功として返ってくるところです。権限のルールで置換が拒否された場合について、同じ形の報告が #80223 に残っています。参照ファイルが1つ欠けただけで、スキルが黙って動かなくなります。

2>/dev/null || echo "(reference missing: ...)" を付けているのはそのためです。欠けていれば本文にその文字列が出るので、呼び出しは中断せずに続き、欠けたこと自体も分かります。

_ を付けた参照を毎回注入する

命名規則は前からありました。毎回読ませたい参照には _ を付ける、というものです。印だけがあって、届ける仕組みが指示文だったので、そこを注入に差し替えました。

documentation、research、lp-review、aso-review、labee-llc-guide、agent-creator、swift-localization の7本が、本文の先頭で _ 付きの参照を cat しています。

GitHubGitHub - LabeeHive/standards: Shared standards repository for Labee LLC projects — a Claude Code plugin marketplace of skills, agents, and workflowsShared standards repository for Labee LLC projects — a Claude Code plugin marketplace of skills, agents, and workflows - LabeeHive/standards

引き換えに諦めたこともあります。

  • 呼び出しのたびに全文のトークンを払います。対象のファイルは1.5KB から6.4KB で、この範囲なら割に合うという判断です
  • 注入は claude.ai から同期したスキルでは走らず、disableSkillShellExecution が設定された環境でも走りません
  • サブエージェントに届くのは、そのエージェントへの指示だけです。SKILL.md がレビュアーにチェックリストを渡す形になっているなら、そのエージェントには今も明示的に読ませる必要があります。毎回届けたい相手がサブエージェントなら、SKILL.md ではなく SubagentStart hook で渡す話になります

注入されたコマンドは権限を聞いてきません。allowed-tools に何も書いていないスキルでも、そのまま走ります。--permission-mode の default と auto の両方で確かめました。止まるのは cat に deny か ask のルールを当てているときで、前処理が落ちれば呼び出しごと黙って中断します。失敗を見えるようにしているのは2つです。欠けたときの || echo "(reference missing)" と、claude -p/standards:documentation を叩いて、注入した節の見出しがそのまま返ってくるかを見る確認です。

fork は名前を変えてほしいと言われている

fork という語を取り違えて、作業中に1回止まりました。名前が実態と合っていないという指摘は以前からあって、#20492 が2026年1月に立っています。

スキルの frontmatter に書く context: fork は、スキルを分離したサブエージェントで走らせる指定で、会話履歴は引き継ぎません。Issue の主張は、fork という語が Unix の fork() や git の fork のように親の状態を複製すると思わせるのに、実際の動きは逆だ、というものです。context: isolated のほうが実態に合う、と続きます。

その後、逆側にも fork が増えました。Agent ツールの subagent_type: "fork" は会話とプロンプトキャッシュをそのまま引き継ぐ指定で、2.1.232 から対話セッションでは既定で有効です。同じ語が、履歴を捨てる側と引き継ぐ側の両方に付いた形です。

Issue はクローズされていて、名前は変わっていません。このリポジトリのスキルは context: fork を設定していないので、混同するのは読むときだけです。

どれに _ を付けるかは、まだ人力で決めている

_ の付いていない参照は、今も「必要になったら読む」という指示のまま置いてあります。そちらを注入していないのは意図的ですが、その判断が正しいと確かめたわけではありません。どの参照が毎回要るかは、書いた人間の見立てだけで決めています。

_ を付けすぎると、使わない内容のトークンを毎回払うだけになります。逆に足りなければ、届いていない参照が黙って残ります。付けすぎなのか足りないのかを知るには、参照ごとに「その呼び出しで実際に使われたか」を数える必要があって、その手立ては今ありません。

disableSkillShellExecution が有効な環境向けの代替も用意していません。その環境ではスキルの本文しか届かないので、中核のルールを本文に直接置くしかなく、そうすると同じ内容が2箇所に増えます。増やす前に、その環境でこのスキルを使う予定があるかを先に確かめます。