メインコンテンツへスキップ
← 記事一覧に戻る
·運用·9 min read

AI agent が skill 規範を破った実録 — Qiita CI 14 回連続失敗と機械ガード設計

GitHub ActionsQiitaCI/CDClaude Code個人開発

結論

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 validateAPI 前チェック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

次に読むならこの導線です

すべての記事を見る
有料で次へ進む¥1,000

【第12回】夜寝てる間に Claude Code が記事を書き上げる構成 — 月 ¥5K で動く全コード

Claude Codeラボ全12話の集大成。Skills/MCP/サブエージェント/Hooks/リモート運用を統合した「自走する Claude 自動化」を、月 ¥5K の実コストで動かす全構成を公開。寝てる間に競合調査・記事下書き・PR まで自動化する 6 層アーキテクチャの完成版。

次の実験記録も追う

Claude Code × 個人開発の実験ログ、失敗、判断変更をまとめて追いたい人向けに、月次でLab Freeを届けます。

masatoman のメルマガ — 毎週月曜の朝に 1 通

masatoman.net で今週公開した記事の中から 1 本を、読者目線で深掘りした手紙が届きます。「自分も同じことやってる」「ここで詰まってた」が見つかる予告編。

この記事が役に立ったらシェア