はじめに
12分13秒。9回。
これが何の数字か、先に種明かしはしません。まずこの記事を書くまでの経緯から話させてください。
この技術記事シリーズ(Zenn・Qiita合わせてこれで20本を超えました)は、テーマ選定・執筆・フォーマット整形・コミット・pushの多くをAIエージェントに任せ、GitHub Actions経由でZennとQiitaに自動公開するパイプラインで運用しています。以前の記事(「AIエージェントに記事投稿を任せたら、自分自身の記事を複製していた話」)では、このパイプラインが投稿の中身で壊れた話を書きました。今回はその手前、パイプラインの土台となるGitHub Actionsワークフロー自体が壊れていた日の記録です。
その日、Qiita側のGitHub Actionsは12分13秒の間に11回走り、そのうち9回が失敗しました。この記事は、そのログを実際に見返しながら書く一次体験レポートです。
TL;DR
- ある日、Qiita CLIの自動公開ワークフローが12分13秒の間に11回実行され、うち9回が失敗した
- 失敗理由は2種類。(1)「内容がQiita上の記事より古い可能性があります」というqiita-cliのブロック、(2)存在しないはずのファイル名を指定してしまい
is not foundで落ちる自作ワークフローの設計ミス - どちらもAIエージェントの暴走ではなく、当時の私(人間)自身が対処のために追加した新しいワークフロー・引数・実験が、次々に新しい失敗を生んだ
- 最終的には、frontmatterの
updated_atを現在時刻に更新して手動で解消し、途中で作った実験用ワークフローは削除して収束させた - 根本原因(なぜQiita側の内容が「新しい」と判定されたのか)は、この記事の時点でも特定できていない。これは対処ログであって、原因究明ログではない
背景:この日、何をしようとしていたか
このリポジトリはもともとZenn版の記事をQiitaにも展開する形で運用を始めています。ある記事(アラジンの魔法のランプ記事)を新規に公開しようとしたところ、qiita publish --allが失敗しました。
これ自体はよくあることとして、まずは原因を切り分けようとしました。「Qiita側で記事が直接編集されていて、ローカルとの差分でpublishがブロックされているのでは」という仮説を立て、その仮説を確認・解消するための仕組みを、その場しのぎで次々に継ぎ足していきました。これが後から見ると、失敗を連鎖させる結果になりました。
何が起きたか:12分13秒の実況ログ
GitHub Actionsの実行履歴を実際に見返すと、次のようになっていました(すべて2026-08-24、時刻はUTC)。
| 時刻 | ワークフロー | トリガー | 結果 |
|---|---|---|---|
| 07:14:39 | Publish articles | push(同期用ワークフローを追加するコミット) | 失敗 |
| 07:14:57 | Sync from Qiita | workflow_dispatch | 失敗 |
| 07:16:22 | Publish articles | push(--all引数の修正コミット) |
失敗 |
| 07:16:39 | Sync from Qiita | workflow_dispatch | 成功 |
| 07:18:22 | Publish articles | workflow_dispatch | 失敗 |
| 07:20:05 | Publish articles | push(単一記事公開ワークフローの追加コミット) | 失敗 |
| 07:20:21 | Publish one article | workflow_dispatch | 失敗 |
| 07:21:54 | Publish articles | push(デバッグ行の削除コミット) | 失敗 |
| 07:22:13 | Publish one article | workflow_dispatch | 失敗 |
| 07:23:26 | Publish one article | workflow_dispatch | 失敗 |
| 07:26:52 | Publish articles | push(updated_at更新コミット) |
成功 |
最初の失敗から最後の成功まで、ちょうど12分13秒。この間に11回のワークフローが走り、成功したのは2回だけ(Sync from Qiitaの1回と、最後のPublish articles)でした。
原因1:「内容がQiita上の記事より古い可能性があります」
最初の失敗(07:14:39)のログを見ると、qiita publish --all --root .の出力にこう出ていました。
aladdin-magic-lamp: 内容がQiita上の記事より古い可能性があります
##[error]Process completed with exit code 1.
qiita-cliは、ローカルのMarkdownとQiita上の記事を比較し、ローカル側が古そうだと判断すると公開をブロックする仕様のようでした。「Qiita側の内容の方が新しい」と判定された正確な理由は、このログだけでは分かりません。差分の比較ロジックの詳細はqiita-cliの実装に依存しており、この記事の時点でも私はそこまで踏み込んで特定できていません。
対処として考えたのが、qiita pullでQiita側の最新内容を一度ローカルに引き込み、差分を解消してから改めてpublishする、という手順でした。これをSync from Qiitaという新しいワークフローとして追加しました。
原因2:存在しないファイルを指定してしまう設計ミス
Sync from Qiitaを作る過程で、最初qiita pull --allと書いていましたが、これがこのCLIバージョン(@qiita/qiita-cli@v1.10.0)では未対応の引数でした(07:14:57の失敗)。引数をqiita pull --root .に直してから成功しています(07:16:39)。
ここまでは順調に見えましたが、次に「ブロックされている記事1本だけを公開できないか」と考え、Publish one articleという別のワークフローを追加しました。ここでqiita publish "loop-engineering.md" --root .のように、ファイル名を直接指定するコマンドを書きました。ところが実行結果はこうでした。
Run qiita publish "loop-engineering.md" --root .
Error: 'loop-engineering.md' is not found
##[error]Process completed with exit code 1.
public/ディレクトリの中を確認すると、loop-engineering.mdというファイルはすでに存在していませんでした。qiita-cliは記事を最初に公開した時点で、ファイル名をQiitaの記事ID(英数字20桁のハッシュ)にリネームする挙動があり、この記事もすでにリネーム済みだったのです。つまり自分で書いたワークフローが、qiita-cli自身の挙動によってとっくに存在しなくなっていたファイル名を指定していた、という単純な設計ミスでした。この失敗は3回繰り返しています(07:20:21、07:22:13、07:23:26)。
最終的な解決
原因1についてはqiita-cli内部のロジックまで追いかけず、frontmatterのupdated_atを現在時刻に手動で書き換えることで、ローカル側を「最新」として扱わせる形で解消しました。あわせてqiita pullで本文差分がないことも確認しています。
原因2のPublish one articleワークフローは、単一ファイル指定のpublishがこのCLIバージョンでは実用に耐えないと判断し、削除しました。
最後に残ったPublish articles(qiita publish --all --root .)を通常のpushで走らせたところ、07:26:52に成功しています。12分13秒、11回の実行を経て、最終的に必要だった変更は「タイムスタンプを更新する」という1行でした。
自己批判:この記事について正直に言うと
いくつか、正直に書いておきたいことがあります。
1つ目は、根本原因を特定できていないことです。「なぜQiita側の内容がローカルより新しいと判定されたのか」は、この記事の時点でも分かっていません。Qiita上で記事が直接編集された形跡は確認できておらず、あくまで「updated_atを書き換えたら通った」という対症療法の記録です。同じ現象が次に起きたときも、同じ手順で直せる保証はありません。
2つ目は、この日の失敗の大半は、AIエージェントの暴走ではなく、当時の私自身が試行錯誤の中で書いたワークフローとコマンドが原因だったことです。以前の記事で書いた「AIエージェントが重複記事を作った」話とは性質が異なり、今回は完全に人間の設計ミスの記録です。パイプラインの信頼性は、AIエージェント側の設計だけでなく、人間が書くインフラコード側の設計にも同じだけ左右される、という当たり前の事実を、あらためて自分のログで確認した形です。
3つ目は、この「12分13秒で9回失敗」という数字自体、後から振り返って初めて意味を持つ数字だということです。渦中にいた当時は、1回1回のログを見ながら「次はこれかもしれない」と個別に対処していただけで、失敗が連鎖しているという全体像を意識してはいませんでした。実行履歴を通しで見返して初めて気づいた数字です。
今日から使えること
自分でCLIツールをGitHub Actionsに組み込んでいる、あるいはこれから組み込もうとしている方向けに、今回の経験から言えることを3つにまとめます。
- エラーメッセージの文言をそのまま信じすぎない。 「内容が古い可能性があります」という表示は、実際の原因(比較ロジックの詳細)までは説明していません。表面的な文言から仮説を立てるのは良いですが、それが唯一の原因だと決めつけると、今回のように対処のためのワークフローが増殖します。
- 外部CLIがファイルをリネームする挙動があるなら、ファイル名をワークフローにハードコードしない。 今回の失敗は、qiita-cliが記事IDにファイル名を書き換える仕様を把握しないまま、元のファイル名を直接指定してしまったことが原因でした。ツールが状態を持って自分でファイルを書き換える設計になっていないか、先に確認する価値があります。
- 失敗が連続したら、いったん増やすのをやめて減らす。 この日の私は、失敗するたびに新しいワークフローや引数を追加していました。振り返ると、最初の失敗の時点で一度手を止めてログをじっくり読んでいれば、11回ではなくもっと少ない試行で収束できたはずです。焦って手数を増やすほど、切り分けは難しくなります。
拙著『AIエージェント設計論 — Harness/Loop EngineeringからRAGまで』では、こうした失敗の切り分けや、自動化パイプラインを壊れにくくするための考え方を全16章で体系立てて整理しています。