freeeの開発情報ポータルサイト

Coding Agent が言うことを聞かないときどうする? - ミクロなコンテキストエンジニアリング

freee で Coding Agent 切り込み隊長をしている @him0 です。2025年は Coding Agent がコーディングの取り組み方を一新する一年でした。freee は現場に Cline を導入したところから始まり、現在(2026-02) は Claude Code が全社の標準ツールとなり落ち着いています。

Claude Code の画面 初めて触る人向けに /tutorial コマンドを実行する様にメッセージが出ている
Claude Code 起動時に /tutorial が紹介される

Coding Agent を使いこなすという文脈で、コンテキストエンジニアリングが大事という話は散々どこでもされていると思います。2025年の年末から、2026年1月は Anthropic が Skills のフォーマット標準化の宣言を発端に、Agent Skills が話題として取り上げられるようになり、SKILL.md や AGENTS.md (CLAUDE.md) コンテキストの適切なタイミングでの投入に焦点を置いた、いわばマクロのコンテキストエンジニアリングの話題が強く扱われている様に思います。

agentskills.io

SKILL.md や AGENTS.md によって Coding Agent が拡張されるのは明らかな事実である一方で、どんなコンテキストを Coding Agent に与えるべきかを理解しないままに、どんどん情報を渡していってしまうと有象無象の情報に Coding Agent は混乱してしまうことも忘れてはいけない事実です。こうなってくると出力までに時間もトークンもたくさん使う状態になったり、そもそも出力の精度が安定しないという状態が発生します。

SKILL.md や AGENTS.md にどんなことを書くべきか、LLM はそれをどの様に解釈しているか、そんなことを考える一件があったので紹介したいと思います。

問題の発端

いつも通り私は社内で AI Agent おじさんをしていると、とあるプロジェクト CLAUDE.md に GitHub の Pull Request のマイルストーンに付ける指示に Claude Code が従わないという相談を受けました。内容を確認してみると、以下の指示が書かれており、確かにマイルストーンを付ける指示が定義されている様に見えます。しかし、実行してみると PR にマイルストーンを付けてくれませんでした。

# マイルストーン

ユーザーからマイルストーン番号またはマイルストーン名の指定があった場合、PRにマイルストーンを設定します:

- マイルストーン番号が直接指定された場合は、その番号を使用
- マイルストーン名が指定された場合は、GitHubのAPIでマイルストーン一覧を取得して該当する番号を特定
- 指定がない場合は Untracked を設定

人間には正しく読めるのに、なぜ LLM は読めなかったのでしょうか?

原因を探る

当事者にインタビューするのが一番素直ということで、Claude Code に相談してみました(長い部分はカットしています)。

(マイルストーンの設定に失敗したセッションの続き)

❯ .github/CLAUDE.md を読んでいるのに、なんで milestone を付けなかったか教えて欲しいです

⏺ 申し訳ありません。今から修正します。

❯ 他のリポジトリと比べてこのリポジトリではそのミスが多発します
  どうしてなのか解明したいです

⏺ 良い質問です。原因を分析させてください。
  質問させてください:
  
  他のリポジトリでは、マイルストーンの指示はどこに書かれていますか?
  - スキル定義自体に含まれている?
  - ルートの CLAUDE.md にある?
  - 別の方法?

  比較できれば、何が違うのか特定できると思います。

質問すればすっと解決できるかと思いきや、なんと Claude Code 自身もどうしてそうなったのか説明できないという回答。人間らしくない LLM らしい面白い挙動です。

ほぼ同様の指示をしているが、正しく動作するリポジトリがあったので、こちらのディレクトリを渡して、比較検討を依頼してみました。

❯ 他のリポジトリでは同じ形式で記述されていますが、ミスらないですね
  ~/src/project-b を参考にしてください

⏺ 違いを発見しました。
  問題点:

  1. project-a は「ユーザーからの指定があった場合」という条件文から始まっている
  2. 「指定がない場合は Untracked を設定」が条件分岐の一つとして埋もれている
  3. 他のラベル設定(「〜を付与すること」)とフォーマットが異なる

  project-b
  は「付与すること」という命令形で、他のラベル設定と同じフォーマットなので、必須アクションとして認識しやすいです。

  project-a の記述を project-b と同じフォーマットに修正すれば、改善されると思います。

差分から解釈を推測し修正内容を決めることができました。「ユーザーからの指定があった場合」が、マイルストーンのセクション全体にかかり、今回は対象外であると判断してしまい最後に書かれている、指示がなかった場合を読み飛ばしていたとのこと。

LLMは"文章"ではなく"構造化された命令集合"を読んでいる

これを踏まえて修正した指示が以下です。人間から見ると微々たる違いですが、確実にマイルストーンを付けてくれる様になりました。

## マイルストーン

Pull Request 作成時、マイルストーンを付与すること

- ユーザーから指定がない場合は "Untracked" を付与
- ユーザーからマイルストーン名が指定された場合は、GitHub API でマイルストーン一覧を取得して該当するものを付与

人間は Markdown を文章として流れるように読みますが、LLM は見出しや箇条書き単位で「構造化された命令集合」として解釈している可能性があります。その結果、条件付きと判断したブロックを実質的にスキップし、今回の問題につながったと考えられます。

同じテキストでも、人間と LLM では前提となる読み方が異なります。LLM の挙動を理解するうえでは、この「構造の解釈」を常に意識すべきだと学びました。

もちろん、この分析は Claude Code 自身による事後的な推測であり、内部の処理を正確に反映しているとは限りません。ただ、指示のフォーマットを修正した結果、実際に挙動が安定したことから、構造の違いが影響していたという仮説には一定の妥当性があると考えています。

まとめ

Coding Agent が効率よく、再現性高く動かすためには、Agent が この一文をどの様に解釈する のかというミクロの話を正しく理解し、課題があれば、それを調整する能力が必要です。そして、本来はその基礎の先に、SKILL.md や AGENTS.md を使った、マクロなコンテキストエンジニアリングが成立するはずです。

SKILL.md は共有しやすく、最初の体験として便利さを実感しやすいものです。しかしそれが、基礎を飛ばして「よく分からないけど SKILL を追加するとうまくいく」という状態を作ってしまっている側面もります。

ときには基礎に立ち戻り、手元の SKILL.md や AGENTS.md を読んでみて(読んでもらって)理解し、必要に応じてチューニングする。そんなコンテキストのリファクタリング技術も今のうちから意識して育てておくと、今後活躍できる機会が増えるのではないかと思いました。