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

記事の自動投稿が静かに止まる事故を、CIに自己修復を仕込んで直した — 既知エラーは自動・未知は人へ

GitHub ActionsCIQiita個人開発自動化

結論

記事の自動投稿(CI)が frontmatter のミスや HTML コメントで止まる事故は、Slack を監視して人が直すより、CI に「既知エラーの自己修復」を仕込む方が安い。直し方が一意に決まる機械的エラー(id 欠落・published: false 残り・コードフェンス外の {/* */})は、検出して落とすのではなく、検出して直して commit してから先に進ませる。

ただし自動修正してよいのは「既知の型」だけ。直し方が一意でないエラーは触らず、失敗させて人へ回す。これは理屈でなく、今回 Qiita の publish が Forbidden(authorization エラー)を返したときに、自己修復が手を出さず正しく失敗した、という形で確認した。

この記事の前提(自分の状況・スタック)

  • 記事を Qiita / Zenn / masatoman.net(Next.js + MDX)/ SNS の複数チャネルに、それぞれ GitHub Actions で自動投稿している個人開発者
  • 各チャネルの CI はすでに「frontmatter を検証して、ダメなら落とす」ゲートを持っている
  • masatoman.net の build-check には以前から「捏造パターンを自動削除して commit & push する」ステップが動いていた(=自己修復パターン自体は既にあった)
  • この記事は、その自己修復パターンを 4 チャンネルへ横展開した1セッション分の実装ログ

読者のよくある詰まり

  • ローカルでは動くのに、CI の publish だけが落ちて記事が公開されない
  • frontmatter の必須フィールドが1つ抜けただけで、--all モードの publish が全記事ごと止まる
  • エラーは出ているが、毎回手で frontmatter を直して push し直すのが面倒
  • 「自動で直してほしい」と思うが、どこまで自動化していいのか線引きがない

実際に起きたこと(4チャンネルへの横展開)

検出から修復への切り替えは、チャンネルごとに「既知エラー → 一意な直し方」が決まっているものだけを対象にした。

masatoman.net(MDX ビルド) コードフェンス外の {/* */} は MDX で本番 500 の原因になる。build-check の検出ステップの手前に、フェンス外のコメントだけを {/* */} に変換して commit & push するステップを追加した。draft 記事でテストしたところ、CI が変換 → github-actions[bot] が commit → build 通過、までを 1 回の run で完了したのを確認した。フェンス内のコード例のコメントは変換せず保持された。

Qiita(frontmatter) id 欠落・id: nullupdated_at 欠落や未クォート・published/published_at の残存・organization_url_name 欠落・slide 欠落 を、validate の手前で機械的に補完するスクリプトを足した。意図的に5種の既知エラーを仕込んだ記事で試すと、5つとも自動修正が発火し、validate を通過した。

Zenn(frontmatter) published: falsepublished_at が同居するとデプロイが止まる。直し方は一意(published: true にする=投稿を通す)なので、「失敗して通知」を「自動修正して commit & push」に昇格させた。

SNS(実行時投稿) ここだけ形が違う。frontmatter ではなく実行時のキュー投稿なので、すでに自動リトライ・隔離・重複キャンセルが実装されていた。最小差分の原則で触らなかった。

原因分析 — なぜ「検出して落とす」だけだと足りないのか

これらのエラーは共通して「直し方が機械的に一意」だ。id が無ければ空文字を入れる、published: falsetrue にする、{/*{/* にする。人が毎回やっている作業に判断は要らない。判断が要らない作業を CI のゲートで止めて人を呼ぶのは、単に投稿が遅れるだけで損をしている。

--all モードの publish は、1記事の frontmatter ミスで全記事の公開が止まる構造を持つ。だから「1つ直せば全部通る」型のエラーほど、自己修復の効果が大きい。

判断基準 — 自動修正してよい型・人へ回す型

今回いちばんの収穫は、自動化が直せなかったケースから出た。

Qiita で意図的エラーを仕込んだ記事は、自己修復で frontmatter が直り validate を通過した。しかしその後の publish API が Forbidden(authorization エラー)を返して失敗した。これは frontmatter のエラーではなく、API の認可・ポリシー側で起きた未知のエラーだ。

ここで重要なのは、自己修復は Forbidden に手を出さなかったこと。frontmatter の既知の型だけを直し、未知のエラーはそのまま失敗として表に出した。設計どおりの動きだった。もし「失敗したら何でも自動で直そうとする」作りにしていたら、原因が分からないまま記事を壊して公開しかねない。

線引きはこうなる。

  • 自動修正してよい = 検証スクリプトが原因を一意に特定でき、直し方が機械的に決まる型(frontmatter 欠落・既知の禁止フィールド・コメント形式)
  • 人へ回す = 直し方が一意でない、または本文や認可など別レイヤーの判断が要るもの(タイトル欠落・タグ0件・API の Forbidden・ビルドの型エラー)

なお、未公開のテスト記事を残すと --all の publish が同じ Forbidden で連鎖失敗する構造があるため、検証後はテスト記事を消してパイプラインを元に戻した。これも「自動化を入れたら、その副作用の後始末まで設計に含める」という同じ話だ。

今日やること(3つ以内)

  1. 自分の publish CI の「検出して落とす」ステップを1つ選ぶ — frontmatter 検証や lint で、毎回同じエラーで止まっているものを探す
  2. その中で『直し方が一意なもの』だけを、検出の手前で自動修正 → commit するステップに置き換える — commit メッセージに [skip ci] を付けて再トリガーのループを防ぐ
  3. 未知のエラーはあえて自動修正しない — 失敗をそのまま表に出して人へ。自動化の範囲は「機械的に直せる型」に閉じる

次に読む

「気づかないうちに静かに壊れていた」系の実録として、GitHub Actions cron が一度も動いていなかった — Node20 と Supabase WebSocket非対応が原因だった話 も同じ「死活確認は仕組みで」の話を書いています。

記事が役に立ったら気軽にどうぞ。

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