AI agent が skill 規範を破った実録 — Qiita CI 14 回連続失敗と機械ガード設計
結論
skill(指示書)に正しいテンプレートを書いても、AI agent はそれを守らないことがある。守らせるには機械ガードが必要。
今回の事故では、masatoman.net の記事公開 agent(night-writer)が Qiita 用 frontmatter の必須フィールドを欠落させた記事を 3 件出力し、qiita-cli --all モードが全停止。10 日間で 14 回連続 CI 失敗を引き起こしました。
同じパターンで過去にも(2026-04-27 に)事故が起きていましたが、skill に「次回は直せ」と書くだけでは再発を止められませんでした。修正後に同じ轍を踏んで初めて、規範を機械化しないと意味がないと腹落ちしました。
この記事でわかること
qiita-cli --all並列 POST が 1 件 500 で止まる仕組みupdated_at引用符欠落・id: nullが CI を 14 回落とし続けた構造- validate-frontmatter.sh を使った CI 段階の機械ガード実装
- Rate Limit エラーを
warning降格して誤検知を排除する設定
何が起きたか(実録)
2026 年 5 月、night-writer が Qiita 用に adapt した記事 3 件の frontmatter に以下の問題がありました。
| 問題 | 件数 |
|---|---|
updated_at の引用符欠落(2026-05-13 → "2026-05-13" が必要) | 2 件 |
id: null(既存記事は UUID 必須) | 1 件 |
qiita-cli publish --all は内部で Promise.all を使い複数記事を並列 POST します。1 件で QiitaInternalServerError (500) が出ると処理全体は中断しますが、並列実行中の他の POST は完走する場合があります。
今回は「CI ログは全件失敗に見えるが、Qiita 側には一部が投稿済み」という不整合状態が生まれました。CI ログだけを見て「まだ公開されていない」と判断し修正→再 push を繰り返した結果、10 日間 14 回連続赤になりました。
2026-05-17 の月次 CI 監査で検出した事故。skill(night-writer の SKILL.md)には「Qiita frontmatter に updated_at/id は必須」と明記されていましたが、実装で守られませんでした。同じ構造的失敗が 2026-04-27 にも起きており、2 回目の再発で機械ガード化を決定しました。
なぜ skill は守られなかったのか
AI agent の skill(= .md ファイルで書いた指示書)は、いわば「人間向けのコードレビューチェックリスト」と同じです。読む人が正しく理解して実行すれば有効ですが、モデルが出力するたびに参照保証はありません。
特に以下のケースで skill は守られにくいです。
- frontmatter のような定型構造: 本文生成に集中すると末尾の細部が崩れる
- 過去事例の未参照: context window に以前の事故ログが入っていない時
- 「次回は直す」系の記述: 機械は「次回」を覚えていない
つまり skill は「期待を文書化する場所」であり、「実行を保証する場所」ではありません。
対策:CI 多段防御
機械ガードを 5 層で構築しました。
1. validate-frontmatter.sh(API 呼び出し前に fail させる)
#!/usr/bin/env bash
set -euo pipefail
ERRORS=0
for f in public/*.md; do
# updated_at は引用符必須
if grep -qP "^updated_at:\s+[0-9]{4}-" "$f"; then
echo "❌ $f: updated_at missing quotes"
ERRORS=$((ERRORS + 1))
fi
# id: null は禁止(新規記事は id フィールド自体を持たない)
if grep -qP "^id:\s+null\b" "$f"; then
echo "❌ $f: id must not be null"
ERRORS=$((ERRORS + 1))
fi
# published_at 残存禁止(Qiita は published_at を持たない)
if grep -qP "^published_at:" "$f"; then
echo "❌ $f: published_at is not a valid Qiita field"
ERRORS=$((ERRORS + 1))
fi
# tags は 1〜5 個
tag_count=$(grep -cP "^\s+-\s+.+" "$f" || true)
if [ "$tag_count" -lt 1 ] || [ "$tag_count" -gt 5 ]; then
echo "❌ $f: tags count must be 1-5, got $tag_count"
ERRORS=$((ERRORS + 1))
fi
done
if [ "$ERRORS" -gt 0 ]; then
echo "Frontmatter validation failed with $ERRORS error(s)"
exit 1
fi
echo "✅ All frontmatter passed"
2. workflow に validation step を追加
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24"
- run: npm ci
- name: Validate frontmatter
run: bash scripts/validate-frontmatter.sh
- name: Publish to Qiita
run: npx qiita publish --all
env:
QIITA_TOKEN: ${{ secrets.QIITA_TOKEN }}
3. Rate Limit / 500 を ::warning:: 降格
- name: Publish to Qiita
run: |
set +e
OUTPUT=$(npx qiita publish --all 2>&1)
EXIT_CODE=$?
echo "$OUTPUT"
if echo "$OUTPUT" | grep -qE "QiitaRateLimitError|QiitaInternalServerError"; then
echo "::warning::Qiita API rate limit or 500 — not a critical failure"
exit 0
fi
exit $EXIT_CODE
CI 赤 = 本当の障害だけに意味を持たせることで、次の事故に気づきやすくなります。
4. 投稿済み記事は ignorePublish: true 固定
一度 Qiita に投稿した記事は frontmatter に ignorePublish: true を付け、以降の --all で更新対象から外します。更新したい時だけ false に戻す運用です。
# public/some-article.md
---
title: "..."
id: "abc123"
updated_at: "2026-05-01"
ignorePublish: true
---
5. 投稿状態の突合確認
CI ログだけを信じず、Qiita API で記事の存在を直接確認します。
curl -s -H "Authorization: Bearer $QIITA_TOKEN" \
"https://qiita.com/api/v2/items/{id}" | jq '.id, .title'
機械ガード設計の考え方
今回の事故を振り返ると、CI が「赤くなったら直す」ではなく「赤くならないようにする」設計が必要でした。
| 層 | 役割 | 今回の実装 |
|---|---|---|
| skill | 期待の文書化 | SKILL.md のテンプレート記載 |
| pre-push | ローカル検証 | (未実装) |
| CI validate | API 前チェック | validate-frontmatter.sh |
| CI error handling | ノイズ除去 | Rate Limit 降格 |
| 手動確認 | 最終保証 | Qiita API 直叩き |
AI agent が生成したコードやファイルを CI に乗せる場合、human review なしで deploy する設計には必ず機械ガードを一段入れるのが原則です。
で、どう稼ぐ?
この記事で扱った「AI agent の品質保証」は、個人開発での収益化と直結します。
masatoman.net では night-writer が月 30 本以上の記事を生成しますが、CI が赤いままでは公開できず機会損失になります。今回の修正で公開パイプラインが安定し、記事量産フローが止まらなくなりました。
AI agent を使った量産フローを回している方、または同じ「skill に書いたのに守られなかった」経験がある方は、CI 多段防御のパターンを参考にしてください。
masatoman.net の Claude Crew Lab(Free プラン)では、AI agent × 個人開発マネタイズの試行錯誤を定期配信しています。
masatoman のメルマガ — 毎週月曜の朝に手紙を 1 通
masatoman.net の今週の記事 1 本を、読者目線で深掘りした手紙が毎週月曜 9:00 に届きます。「これ自分のことだ」が見つかる予告編。登録特典に「個人開発の収益化チェックリスト 15 項目」。
masatoman のメルマガ — 毎週月曜の朝に 1 通
masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。
Next Step
次に読むならこの導線です
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
【第8回】Claude Code で GitHub Issue から PR まで自動化する実装ガイド
GitHub Issue が立った瞬間に Claude Code が実装し、PR を作成するまで自動化する仕組みを、実コード付きで解説。GitHub Actions + claude-code-action の組み合わせで、Issue 駆動開発を実現します。
【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード
Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。
Claude Code で 0→MVP を1日で作る全記録 — recipe-ai Build in Public
Claude Codeを使い、YouTube料理動画からレシピを自動抽出するAIアプリ「recipe-ai」を0からMVPまで1日で構築した全記録。CLAUDE.md設計、API実装、Supabase連携、Vercelデプロイ、Stripe課金導入までの工程を時系列で公開。
次の実験記録も追う
Claude Code × 個人開発の実験ログ、失敗、判断変更をまとめて追いたい人向けに、月次でLab Freeを届けます。
masatoman のメルマガ — 毎週月曜の朝に 1 通
masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。
この記事が役に立ったらシェア