Codex が SKILL.md を 220 行で打ち切っていた話
Quaere という skill 集を作っている。コーディングエージェントが雑になりやすい箇所にゲートを置く、5本の skill だ。
作っているうちに、確かめておきたいことが出てきた。書いた skill を、エージェントは本当に最後まで読んでいるんだろうか。
Terminal-Bench を回したときのログを見て、Codex CLI が skill ファイルをどう読んでいるか、その実コマンドを全部拾ってみた。結果は、わりと身も蓋もなかった。
quaere-evidence という skill は 441 行ある。Codex はそのうち 220 行を読んで、残りの 221 行を、一度も読んでいなかった。
220 行で、きっかり止まる
Codex がローカルのファイルを読むときは、シェルコマンドを使う。skill ファイルもそうで、ログを見るとこういうコマンドが並んでいる。
sed -n '1,220p' ~/.agents/skills/quaere-evidence/SKILL.md
sed -n '1,220p' は「1行目から220行目までを出す」という意味。221行目以降は、このコマンドの出力には入らない。
一度きりなら、たまたまそういうコマンドを選んだ、で済む話だ。でも、ログを通して見るとそうじゃなかった。skill を読んだ 41 タスクのうち、39 タスクが、読み取りの最も深い行をきっかり 220 に揃えていた。
念のため補足しておく。これは「一手目がたまたま 220 だった」という話ではない。どのタスクでも、221 行目から先を取りにいく二手目 ── sed -n '221,441p' のようなコマンド ── は出ていなかった。読み取りは 220 で終わっている。残る 2 タスクも、220 ちょうどではなかっただけで、やはり 200 行台のどこかで止まっていて、skill の末尾までは届いていない。120 でも 441 でもなく、毎回 200 行台の前半。偶然でこの一致は出ない。
この計測は、Quaere リポジトリの evals/ にある runner で Terminal-Bench を回し、各タスクに残る sessions/agent.log を見れば確かめられる。skill を読む sed コマンドは、そこにそのまま記録されている。
Quaere の skill は、いまのところどれも 220 行を超えている。つまり全部、後半が構造的に読まれないまま走っていたことになる。
| skill | 行数 | 読まれない割合 |
|---|---|---|
| quaere-semantic | 256 | 14% |
| quaere-audit | 299 | 26% |
| quaere-execution | 302 | 27% |
| quaere-grounding | 357 | 38% |
| quaere-evidence | 441 | 50% |
しかも、220 行より後ろに置かれていたのは ── anti-patterns、想定される drift のパターン、stop condition、他の skill との連携手順。要するに「こういうふうに雑になるな」「ここまで来たら止まれ」を書いた、いちばん指示の強い部分だった。Quaere はエージェントを止めるための skill なのに、その「止めろ」の側が読まれていなかった。
これは Codex のバグなのか
最初は、Codex CLI が悪いんだと思った。sed -n '1,220p' という打ち切りを、Codex のファイル読み込み処理が固定でやっているんだろう、と。
そうじゃなかった。
Codex CLI には、ファイル読み取り専用のツールがない。ファイルを読むということは、シェルコマンドを生成して実行することと同じだ。だから、エージェントが skill を自分で拾いにいく経路では、「SKILL.md の本文を読む」が「sed を打つ」になる。そして sed -n '1,220p' の文字列は、Codex CLI が出しているのではなく、モデルが tool_call の arguments として組み立てたものだった。もしハーネス側が 220 で切っているなら、ファイルはハーネスが直接渡していて、tool_call のコマンド文字列に sed なんて登場しない。sed が登場している時点で、コマンドを書いているのはモデルのほうだ。
つまり 220 という数字は、Codex のどこかに書かれた設定値ではなく、モデルの挙動から出てくる。
別の角度からの裏取りもある。skill ツール呼び出し型のハーネス、つまり Claude Code や OpenCode 経由で同じ skill を使うと、220 問題は起きない。これらのハーネスでは skill 本文を harness 側が事前に丸ごと読み込んでモデルに渡している。Claude Code の transcript 57 件で全数検証 (441 行の skill も毎回 441 行で配信されていた)、OpenCode のバイナリ実装にも skill 本文を <skill_content> ブロックで全文返すコードがそのまま埋まっている。これらの経路では sed の出番がないので、cap が表に出る余地もない。Codex CLI のような「ファイルを読むならモデル自身がシェルコマンドを書く」アーキの中だけで、220 という上限が見えてくる。
ここで一点、留保しておく。「同じ Codex CLI ハーネスで、動かすモデルだけ Claude などに差し替えたら 220 が消えるか」は、今回測れていない。Codex CLI は実装上 OpenAI のモデルに紐づいているし、別モデルを強引に載せた比較も手元にない。ここまでで言えるのは「sed の文字列はモデルが出している」「skill ツール経路では起きない」までで、「Codex CLI 自体は無罪」とまでは言い切れない。
逆方向の差し替え ── モデルは gpt-5.5 のまま、ハーネスだけ Claude Code 側に差し替える ── は claudex で測れた。claudex は Claude Code を OpenAI 互換 endpoint に向けて起動するラッパで、tool 形状と prompt は Claude Code のまま、推論は gpt-5.5 が担当する。3 セッション走らせて、3 件全部で first move は Skill tool の呼び出し になった。sed -n '1,Np' SKILL.md は 0 件。さらに 2 件では、Skill(...) の直後に SKILL.md 全文 (frontmatter のみ落とした 439 / 259 / 353 行) が user-message envelope で次のターンに挿入され、model はそれを読んでから具体作業に入っていた。残り 1 件は Skill 実行が claudex 側で error を返したが、それでも model は sed には落ちず、Glob/Bash/Agent で進めた。
つまり 220 は「gpt-5.5 が無条件にやること」ではなく、Codex CLI が skill 専用 tool を持っていないから model が shell に落ちる、その shell イディオムが 220 行で出る ── という harness-shape 側の挙動だ、と言ってよさそうだ。n=3 の小さな観察なので断定はしない。
ついでに、Skill から Skill を呼ぶ連鎖の側にも cap は等しく効いていた。Codex セッションを当たり直すと、Skill A の body の handoff 記述を読んだ model が、続けて Skill B (と C) を読みにいく挙動は実際に起きている。代表例の 3-chain では、quaere-audit → evidence-gated-review → external-grounding を続けざまに sed -n '1,220p' で読んでいた。chain の 1 件目だけ 220、2 件目以降は別の数字、ということは無かった。全 hop が 220 で揃う。これは「Codex は最初の skill だけ浅く読む」のではなく、「skill を 1 本読むときの cap がそのまま chain にも適用される」ことを意味する。
結果として、SKILL.md の handoff 記述自体が 220 行を超えた位置にあると、その chain は起きない。手元の Quaere 5 skill で他 skill 名が最初に登場する行を拾うと、quaere-audit と quaere-execution は全部 220 内に入っているが、quaere-semantic は 4 つのうち 3 つが line 221 ── 1 行差で構造的に届かない。Codex 経路での chain 率 (7%) が Claude Code/OpenCode 経路 (19%) より低いのは、おそらくこの authoring 側のずれが効いている。
なぜ 220 なのか
ここを理解するには、Codex の skill の仕組みを少し見ておく必要がある。
Codex の skill は progressive disclosure で動く。起動時に読み込まれるのは、各 skill の名前・説明・ファイルパスだけ。本文(SKILL.md の中身)は、エージェントがその skill を使うと決めたときに初めて読まれる。
このとき「本文をどう読むか」が、呼び出し方で変わる。codex-rs のソースを読み解いた takiko 氏の記事 によれば、ユーザーが skill を明示的に指定した場合は、本文がハーネス側から自動で注入される。一方、自然言語で頼んで、エージェントが自分で skill を選んだ場合は ── LLM が自分で本文を読みにいく。
そして Codex には、ファイル読み取り専用のツールがない。ファイルを読むということは、シェルコマンドを生成して実行することと同じだ。だから自然言語呼び出しの経路では、「SKILL.md の本文を読む」が「sed を打つ」になる。私の eval は、エージェントが skill を自分で拾う側 ── つまり後者の経路だった。だから sed が出ていた。
では、なぜその sed がいつも 220 なのか。
skill を読むという状況が、毎回ほとんど同じだからだと思う。一般のコードファイルを読むときは、120 行だったり 240 行だったり、数字がばらつく。タスクも理由もその都度違うからだ。でも skill を読むときのモデルの状況は一種類しかない ──「skill システムが、このパスの skill を使えと言っている」。同じ状況が入れば、同じコマンドが出る。学習された「skill を読むコマンド」の最頻値が、毎回そのまま出てくる。
そして、ここがいちばん大事なところ。sed -n '1,220p' は、モデルが「220 行で打ち切れ」と上限をかけているわけじゃない。あれはモデルにとって、「SKILL.md を一本まるごと読む」ためのイディオムなんだと思う。220 ≈ モデルが思っている「SKILL.md の長さ」だ。
Agent Skills の標準は、もともと本文を短く保つ設計になっている。長い仕様やサンプルは references/ に逃がして、SKILL.md 本体には「いつ使うか」「どう進めるか」だけを書く。その progressive disclosure に沿って書かれた SKILL.md は、だいたい 200 行に収まる。だから 220 は、「まともな skill の長さ + 少しの余白」くらいの数字になる。
訂正: ここで「だいたい 200 行に収まる」と書いた前提は仕様読み込みが浅かった。Agent Skills の公式仕様は本文上限を 500 行 / 5000 tokens と推奨しており、220 はその半分以下でしかない。さらに仕様は activation 時に本文を 丸ごとロードするよう agent に求めている。220 は「仕様が想定する skill の長さ」ではなく、model が学習データから引いてきた「自分が見慣れた SKILL.md の長さ」 ─── 仕様の許容ラインを大きく下回る独自イディオム ─── と理解するのが正しい。
モデルが 221 行目を読みにいかないのは、自分の中ではもう skill を全部読み終わったつもりでいるからだ。仕様どおりに書かれた skill なら、実際それで全文読めている。問題が起きるのは、モデルが想定している長さを超えた skill ── つまり、Quaere のような skill だけ。
訂正: ここも「仕様どおりに書かれた skill なら全文読めている」と書いてしまったが、これは間違い。仕様の上限は 500 行で、それより手前で読み終えたつもりになっているのは model 側の癖であって、仕様の側の話ではない。「全文読み終わったつもり」と「仕様の要求 (entire-file load)」が乖離していることに気付けていなかった。
正直に線を引いておくと、「なぜ 200 でも 250 でもなく 220 なのか」という最後の一桁までは、私には裏が取れていない。学習データ上の最頻値が 220 に尖った、というところまでしか言えない。ただ「220 はモデルが想定する skill の長さ」という方向は、計測の安定具合(39/41)とも、仕様の作りとも、噛み合っている。 ただ「220 はモデルが想定する skill の長さ」という方向は、計測の安定具合(39/41)と整合する。仕様 (500 行) とは噛み合っていない。
つまり、仕様どおりに書けばいい つまり、spec-violating な cap を回避するように書くしかない
この話の落とし所は、「Codex にもっと読ませる方法」ではない。
220 行の打ち切りは、踏んではいけないバグを踏んでいるわけじゃなかった。モデルが「skill は短いものだ」と ── Agent Skills の仕様どおりに ── 前提していて、それを超えた skill を書いた側が、その前提から外れているだけだ。直す方向は、Codex を変えることじゃなく、モデルの前提に合わせることになる。
訂正: 220 行の打ち切りは、仕様 (本文を丸ごとロードする) には合っていなかった。仕様どおりではなく、仕様の prescription に対する model 側の deviation。「Codex を変えるか、モデルの前提に合わせるか」の二択も書き方が悪い。仕様に合っているのは前者 (Codex/model を直す方) で、後者は workaround。ただし利用者の立場で いま 動かす分には、後者の workaround のほうが現実的な落とし所になる、という順序が正しい。
計測値そのものは 220 だった。ただ、本文で書いたとおり 220 はマージン込みの数字で、モデルが素で想定している長さはもう少し手前 ── だいたい 200 行前後だと思う。だから、自分で上限を引くなら 200 に取っておくのが安全だ。
計測値そのものは 220 だった。仕様の許容ラインは 500 行だが、Codex 経由で gpt-5.5 を使うなら、220 cap を回避するために 200 行以内に収めるのが安全 (= spec ではなく cap に合わせる workaround)。
- SKILL.md 本体は 200 行以内に収める (workaround as of gpt-5.5 + Codex CLI v0.128.0)
- Iron Law、使う場面、コア手順、最小限の例は、最初の 200 行に入れる
- 長い例、anti-patterns、参照ドキュメントは references/ に逃がす
これは目新しい話じゃない。「SKILL.md は短く」は、OpenAI の公式ドキュメントにも書いてある。ただ、それが「行儀のいい書き方」ではなく「守らないと後半が物理的に読まれない」制約だった、というのは、私にとっては実物のログを見るまで腹に落ちていなかった。
「SKILL.md は短く」自体は仕様にも書いてある。ただし仕様が言う「短く」は 500 行 / 5000 tokens で、私が観測した 220 cap よりずっと緩い。200 行内に押し込めという advice は、仕様の表現する「短く」ではなく、それよりもう一段厳しい現場 cap に対する workaround だ ── という線引きを、最初は混同して書いてしまった。
そして、これは Quaere 自身に跳ね返ってくる。
Quaere の skill は 256〜441 行ある。リポジトリの README にある eval の数字 ── in-tree eval で +37.7pp、Terminal-Bench でも clean 69-task subset で +8.7pp ── は、どちらも codex exec 経由で測ったので、本文で書いた 220 cap がそのまま適用される。いま振り返ると、あれは「Codex が後半を読まないまま走った skill」を測った数字だった。しかも読まれていなかったのは、stop condition や anti-patterns といった、いちばん「止めろ」を書いた部分。Codex は、ブレーキの説明書の後半を伏せたまま、Quaere を走らせていたことになる。
これが「全文読まれていればもっと数字が良かった」のか、「最初の 200 行で実質足りていた」のかは、いまのデータだと区別がつかない。skill を 200 行以内に収めた版で測り直すのが、次にやることだ。それまでは、あの数字は「Codex が切り詰めた Quaere」の数字、という但し書きが要る。
書いた SKILL.md と、エージェントが実際に読む SKILL.md は、同じ長さでないかぎり別の文書になる。221 行目から先は、自分が書いたつもりでも、相手には届いていない。skill を短く保て、というのは、たぶんそういう意味だ。
おわりに
この記事は、takiko 氏の「Codex CLI は Agent Skills をどう実装してるのか」── loader の実装そのものを追った記事 ── の、挙動側からの続きのつもりで書いた。実装編に対する、挙動編。
計測の生データは、別のノートにまとめてある:
計測に使った skill 集 Quaere はこちら。
Discussion
claudex!!
今回の検証に使わせていただきました〜。
ありがとうございます〜。
記事の内容はたいへん素晴らしいと思います。
ただ最後の節のみ気になったのですが、Agent Skillの公式仕様に以下のような記載があるので、「220が仕様通り」とは言えないのではないでしょうか?
指摘のとおりで 500 行は仕様の明示値で、その意味で skill は逸脱していませんでした。
記事が『仕様どおり』と書いたのは不正確でした。
申し訳ないです。正しくは『モデルの prior が仕様より厳しい』で、むしろ仕様の 500 行を安全圏と思って書くと Codex が後半を落とす、というのが実測から読み取れる意味合いですね。
sedのスクショに反論しようとしたら消えてた、、、