この記事で解決できること
- LLMがfrontmatterテンプレートを守れないことによるCI連続失敗の根本原因
- push前に決定論的ゲート(validate-frontmatter.sh)を差し込む実装方針
- ローカルのSKILLファイルを直しても自動化が直らない理由
環境
- Claude Codeクラウドルーチン(content-executor)で記事を自動生成
- Qiita CLI + GitHub Actions(qiita-publish.yml)で自動投稿
- 事故発生日: 2026-06-19頃
問題:Qiita/Zenn CIが再び連続失敗した
2026-05-17にQiita/Zenn CIのfrontmatter欠落事故が発生し、CIに検証ガードを入れた。そのガードが「設計通りに」働いてpushを止め続けた——つまり再発した。
原因は以下の構造だった。
| 層 | 動作 | 限界 |
|---|---|---|
| テキスト指示(プロンプト) | LLMに「このフォーマットで」と伝える | 守る「努力」はするが保証はない |
| CIガード(push後) | 壊れた記事を検出して止める | pushは通る。失敗ログが積み上がる |
| push前ゲート(今回の解決策) | 生成直後・push前に機械的に検証する | 壊れた状態でpushされることを原理的に防ぐ |
原因:LLMはfrontmatterテンプレートを「守ろうとするが保証しない」
content-executorがQiita/Zenn向けのadapted記事を生成する際、LLMはfrontmatterテンプレートを「知っている」。しかし確実に守るわけではない。
- フィールドを一部省略する
- 過去に見た形式(
published: falseやpublished_at:など)を混入させる
これはプロンプトの問題ではなくLLMの構造的な動作だ。指示文を厚くするアプローチには限界がある。
解決策:Step5にvalidate-frontmatter.shを埋め込む
content-executorのStep5(外部配信adapted生成後)にvalidate-frontmatter.shを必ず通す構成に変更した。
# Step5の処理順(変更後)
# 1. Qiita向けadapted記事を生成(LLM)
# 2. cd Qiita && git pull --rebase
# 3. push前ゲート(新設)
bash scripts/validate-frontmatter.sh public/{slug}.md
# → NGなら自動補完を試みてexit0まで繰り返す
# → 通らなければpushをスキップ
# 4. exit0確認後にpush
validate-frontmatter.shは以下をチェックする:
- 必須フィールドの存在(
updated_at、id、organization_url_name、slide) - 禁止フィールドの不在(
published、published_at) -
id: nullではなくid: ''であること
これで「LLMが正しく出力したか」をLLMに確認させるのではなく、機械が検証する境界を作れる。
付記:ローカルのSKILLファイルを直しても何も変わらなかった
生成側を修正しようとしてローカルのSKILL.mdを書き換えたが、何も変わらなかった。
実際に記事を生成しているのはクラウド上で稼働しているcontent-executorルーチンだった。ローカルのSKILL.mdは参照されていない。authoritativeな設定はクラウド側のスケジュール設定にある。
自動化の設計では「どのファイルが実際に参照されているか」を事前に把握しておくことが必須だ。
まとめ:守らせたい制約はゲートに昇格させる
指示文 → ゲート への昇格基準:
- 崩れたとき下流システム(CI)が止まる制約
- 機械的に検出・検証できる制約
- 同じ崩れが2回以上発生した制約
プロンプトを厚くするより、push前のシェルスクリプト1本の方が信頼できる。
AI ツールを業務に組み込む設計の実験ログを書いています。 https://masatoman.net