LLMの自動化は指示文でなく決定論的ゲートで守る — content-executor Step5 実装記録

「指示文を直したのに、また同じエラーが出た」——そう思ったことがあるなら、この話は自分ごとになるはずです。
結論
LLMへのテキスト指示は、フォーマット制約を守る保証にならない。守らせたい制約は、push前の決定論的ゲート(シェルスクリプト)で機械的に止める。
これが、2026年6月のQiita/Zenn CI再発事故から私が出した答えです。
この記事の前提
- スタック: masatoman.net(Next.js + Supabase) + Qiita/Zenn 外部配信
- 運用: content-executorというクラウドルーチンで記事を自動生成→各媒体にadapt→push
- 事故日: 2026-06-19頃(Qiita/Zenn CIが連続失敗)
- 「実装して成功した話」ではなく「再発事故を受けてどう設計変更したかの判断記録」です
読者のよくある詰まり
- LLMに「このフォーマットで出力して」とプロンプトに書いたのに、たまに崩れる
- 崩れるたびにプロンプトを修正するが、また別の場所で崩れる
- 「指示を徹底すれば防げる」と思い込んで、同じ作業を繰り返している
これはプロンプトの問題ではありません。LLMの構造的な問題です。
実際に起きたこと
Qiita/Zenn CIが連続失敗した。原因はfrontmatterのフィールド欠落とpublished_atの混入。
2026-05-17の事故を受けてCIに検証ガードを入れた。そのガードが「設計通りに」働いてpushを止めていた——つまり再発した。
再発の真因はこうだ。
content-executorがQiita/Zenn向けのadapted記事を生成する際、LLMはfrontmatterテンプレートを「知っている」が「確実に守る」わけではない。 フィールドを一部省略したり、過去に見たフォーマットを混入させたりする。テキスト指示でテンプレートを渡していたが、LLMはそれを「参考情報」として扱う。
もうで1つの発見があった。「生成側を直そう」としてローカルのSKILL.mdを修正したが、何も変わらなかった。実際に記事を生成しているのはクラウド上で稼働しているcontent-executorルーチンであり、ローカルのSKILL.mdは参照されていなかった。authoritative な設定はクラウド側のスケジュール設定にある。ローカルで何を直しても、クラウドルーチンが読むファイルを変えなければ無意味だった。
原因分析
問題の構造を整理すると:
| 層 | 何をしているか | 限界 |
|---|---|---|
| テキスト指示(プロンプト) | LLMに「このフォーマットで」と伝える | LLMは守る「努力」をするが保証はない |
| CIガード(push後) | 壊れた記事を検出して止める | push後なので記事は生成済み。失敗ログが積み上がる |
| push前ゲート(今回の解決策) | 生成直後・push前に機械的に検証 | 壊れた状態でpushされることを原理的に防ぐ |
LLMを使う自動化において、「指示文を厚くする」アプローチは限界がある。LLMの出力を信頼するのではなく、出力の境界に決定論的な検証を置くのが正しい設計だ。
実装した判断:Step5にゲートを埋め込む
解決策は「content-executorのStep5(外部配信adapted生成後)にvalidate-frontmatter.shを必ず通す」だった。
Step5の処理順(変更後):
1. Qiita向けadapted記事を生成(LLM)
2. cd Qiita && git pull --rebase
3. bash scripts/validate-frontmatter.sh public/{slug}.md ← ここが新設ゲート
→ NGなら自動補完を試みてexit0まで繰り返す
→ 通らなければpushをスキップ(記事は生成されるが配信されない)
4. exit0確認後にpush
validate-frontmatter.shはシェルスクリプトで、フィールドの存在・型・禁止フィールドを機械的にチェックする。LLMの気分に左右されない。通れば進む、通らなければ止まる——それだけだ。
重要な設計原則: ゲートは「LLMが正しく出力したかの確認」ではなく「機械が壊れた状態を通さない境界」として機能させる。LLMを信頼しないのではなく、LLMの出力に依存する部分を最小化する。
判断軸:どの制約を「ゲート」にするか
すべての制約をゲートにするのは過剩だ。以下の基準で判断する:
ゲートにすべき制約:
- フォーマット・スキーマの制約(frontmatterのフィールド存在・型)
- 下流システムが依存する制約(CIが読む、DBが期待するスキーマ)
- 崩れたときに自動修復が難しい制約(publishされた記事のURL/slugの変更)
テキスト指示で十分な制約:
- 内容・文体・トーン(ある程度崩れても手動修正できる)
- 創造的判断を伴うもの(ゲートで測定できない)
判断の問い:「これが崩れたとき、機械的に検出して止められるか?」Yesならゲートにする。
ローカルとクラウドの乖離問題
もうで1つの教訓として記録する。
自動化を設計するとき、「どのファイルが実際に参照されているか」を必ず確認する。私の場合:
- ローカル:
~/home/work/claude-code/SKILL.md(手で編集できる) - クラウドルーチン: スケジュール実行時にロードされる設定(別の場所にある)
ローカルのSKILL.mdを完璧に書いても、クラウドルーチンがそれを読まなければ意味がない。「どこが本当のauthoritativeか」を把握せずに修正しても、デバッグに時間を湶かすだけだ。
今日やること(3つ以内)
-
自分のLLM自動化パイプラインで「フォーマット崩れが起きたら下流が壊れる」箇所を1つ特定する。 その箇所のpush前に検証スクリプトを入れることを検計する。
-
「テキスト指示を直す」を繰り返している制約がないか確認する。 同じ崩れが2回以上起きたなら、指示ではなくゲートで対処する番だ。
-
クラウドで動いている自動化がある場合、「実際に参照されているファイル」を1つ確認する。 ローカルのコピーと乖離していないか。
masatoman のメルマガ — 毎週月曜の朝に手紙を 1 通
masatoman.net の今週の記事 1 本を、読者目線で深掘りした手紙が毎週月曜 9:00 に届きます。「これ自分のことだ」が見つかる予告編。登録特典に「個人開発の収益化チェックリスト 15 項目」。
masatoman のメルマガ — 毎週月曜の朝に 1 通
masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。
記事が役に立ったら気軽にどうぞ。
この記事が役に立ったらシェア