AIエージェント開発・運用の事故録 #07
CIが落ちても、
すぐrerunしない
failure signatureの分類とfresh exact-head CI
公開日:2026年8月25日 著者:飯田 友広(Tomohiro Iida)
この記事の結論
CIの失敗はrerunの前に分類します。deterministic、flaky、infrastructure、rate-limit、unknownの5つで扱います。
最初のfailure evidenceを保存してから操作します。新しいcommitが古いrunをcancelすると、最初のログは読めなくなります。
リソース制約由来の失敗を「1ファイルrevertで通った」と因果で読みません。差分とリソース設定の両方を動かして比較します。
rerunしてよいのは、evidenceとsignatureが残っていて、分類がflakyまたはrate-limitで、直前にcodeを変更していないときだけです。
code変更後は前回の緑を再利用せず、fresh exact-head CIを回します。
CIが赤くなった直後に取れる最短の行動は、rerunボタンを押すことです。rerunで緑になる失敗は実際に存在します。
問題は、緑になった理由を説明できないまま次へ進むことです。
結論は単純です。
CIの失敗は、rerunする前に分類します。 最初のfailure evidenceを保存し、failure signatureを抽出し、deterministic、flaky、infrastructure、rate-limit、unknownのどれかへ割り当てます。分類が決まってから、rerunしてよいか、code修正が必要か、人間の判断へ上げるかを選びます。
Incident Card
INCIDENT:
CI失敗の原因を特定しないままrerunし、緑になった結果だけを根拠に先へ進んだ
SYMPTOM:
同じPRで赤と緑が入れ替わり、どの失敗が解決済みでどれが未解決かを説明できない
FALSE_ASSUMPTION:
rerunで通ったのだから、元の失敗は無害だった
ROOT_CAUSE:
失敗が一度きりの出来事として扱われ、evidence保存、signature抽出、分類、rerun前提条件が無かった
IMMEDIATE_FIX:
rerunの前にjob、step、exit code、ログの先頭と末尾を保存し、分類を1つ選んで根拠を1行残す
SYSTEM_FIX:
分類ごとに許可される操作を固定し、code変更後はfresh exact-head CIを必須にする
REMAINING_RISK:
分類自体を誤ると、誤った分類のまま自動化が進む赤いCIを、押し直しで消していた
観測できた失敗は、性質がまったく違うものが同じ赤色で並んでいました。
2026-08-23、GitHub Actionsのinstallation tokenのprimary rate limitにより、PR headの素性を確認するworkflowがHTTP 403で停止しました。同じ日の実測では、SIGNAL PR Build Evidenceが95分で100 runを起動し、39が成功、60が403で失敗しています。この失敗はPRの内容と無関係ですが、commit statusにはfailureとして残ります。
別の失敗では、typecheckがexit 134で終了しました。これはtscがNode既定の約2GBヒープを超えたheap OOMです。さらに、Git worktreeを共有checkout直下に増やした時期には、Node heapを8GBへ引き上げてもOOMが再発しました。原因はメモリ不足そのものではなく、ESLintとTypeScriptの解析対象に同じrepositoryの完全コピーが複数入っていたことでした。
同じ期間に、lint error、型エラー、assertion failureのような、同じ入力なら必ず再現する失敗も出ています。
これらを区別せず「とりあえずrerun」で扱うと、通ったり通らなかったりします。そして、通った回だけが記録に残ります。
誤った前提は、二つ重なっていた
一つ目は、rerunで緑になったなら元の失敗は無害だったという前提です。rerunが変えるのは実行環境と時刻であって、コードではありません。緑は「今回は再現しなかった」ことしか示しません。
二つ目は、直前に触った差分が原因であるという前提です。これはリソース制約由来の失敗で特に危険です。OOMやrate limitは、入力量や呼び出し頻度がわずかに増減しただけで閾値を跨ぎます。1ファイルをrevertして通った場合、原因はそのファイルではなく総量である可能性があります。ヒープ量や並列度を固定したまま差分だけを動かすと、因果と相関を取り違えます。
切り分けるには、差分とリソース設定の両方を動かして比較します。片方だけを動かした1回の観測を根拠にしません。
根本原因
失敗が「一度きりの出来事」として扱われていたことが根本原因です。具体的には次が欠けていました。
- 最初の失敗のevidenceが保存されない
- exit codeとメッセージからfailure signatureを抽出していない
- signatureを分類する定義が無い
- rerunに前提条件が無く、誰でも何回でも押せる
- 同じsignatureが何回出たかを数えていない
最後の項目が欠けると、flakyとdeterministicを区別できません。1回目の赤と5回目の赤は、見た目が同じでも意味が違います。なお、CIの実行そのものをパス単位で分岐させる設計と、走らせなかった検査の扱いは#08が扱います。
その場の対処
rerunを押す前に、次の3つだけを先に済ませます。
- 失敗したworkflow、job、step、exit code、ログの先頭20行と末尾50行を保存する
- exit codeとエラーメッセージからfailure signatureを1行で書く
- 分類を1つ選び、その分類を選んだ根拠を1行書く
この3つは、赤い実行が消える前に取ります。concurrencyの設定によっては、新しいcommitが古いrunをcancelして、最初の失敗ログが読めなくなります。
保存する項目を固定する
「ログを見ました」では、後から同じ失敗かどうかを判定できません。保存する項目を先に決めます。
FAILURE_EVIDENCE:
workflow:
run_id:
run_attempt:
job:
step:
exit_code:
subject_sha:
started_at:
runner:
log_head:
log_tail:run_attemptとsubject_shaは必ず含めます。この2つが無いと、「同じSHAで結果が揺れた」のか「別のSHAで別の失敗が出た」のかを後から区別できません。flakyの判定はこの区別に依存します。
signatureは、可変部分を落としてから作る
生のログをそのまま比べると、同じ失敗が毎回違う文字列になります。時刻、run id、一時ディレクトリのパス、所要時間、行番号のように、実行のたびに変わる部分を落とします。
残すのは次の3要素です。
- exit codeまたは終了シグナル
- エラー種別を決める語(
heap out of memory、rate limit、not ok、型エラーのコードなど) - 発生したjobとstepの名前
この3要素を連結した1行がsignatureです。粒度が細かすぎると、同じ失敗が別物として数えられます。粗すぎると、性質の違う失敗が同じ束に入ります。目安は、そのsignatureを見て次の操作を選べるかです。選べないなら粗すぎます。
仕組みへ変換する
分類ごとに、許可される操作と禁止される操作を固定します。
| 分類 | 判定材料 | 次の操作 |
|---|---|---|
| deterministic | lint error、型エラー、assertion failure | rerun禁止。code修正後にfresh CI |
| flaky | 同一SHAで結果が揺れる | 1回だけrerun可。台帳へ登録し修正課題を作る |
| infrastructure | runner起動失敗、network、exit 134 | 解析対象と実測から原因特定。増量だけで終わらせない |
| rate-limit | HTTP 403、429、rate limit文字列 | 限定回数の自動再試行のみ。成功を偽装しない |
| unknown | 上のどれにも当てはまらない | rerunせず人間判断へ上げる |
分類ごとの判定材料
deterministicは、同じ入力なら必ず同じ結果になる失敗です。lint error、型エラー、テストのassertion failureが該当します。判定材料は、同一SHAで2回実行しても同じstepが同じexit codeで落ちることです。
flakyは、同一SHA、同一設定で結果が揺れる失敗です。判定材料は、同じsignatureに対してrun_attemptの異なる成功と失敗の両方が存在することです。1回の観測では判定できません。
infrastructureは、runnerの起動失敗、network、ディスク、メモリのように、実行基盤側の制約で起きる失敗です。exit 134はこの分類の代表例で、Node既定の約2GBヒープを超えたheap OOMを示します。判定材料は、失敗がコードの意味ではなく資源の量に依存していることです。
rate-limitは、外部APIの呼び出し枠を使い切ったときの失敗です。判定材料はHTTP 403、HTTP 429、そしてレスポンス本文のrate limitという語です。同じ枠を使う別のworkflowが同時期に大量に走っていれば、分類はさらに確からしくなります。
unknownは、上のどれにも当てはまらない失敗です。判定材料が無いこと自体が判定結果です。無理に他の4つへ寄せません。
rerunしてよい前提条件
rerunは、次をすべて満たしたときだけ実行します。
- failure evidenceを保存済みである
- signatureを1行で書けている
- 分類がflakyまたはrate-limitである
- その分類を選んだ根拠を1行で書いている
- rerunの結果に関係なく、修正課題を作る予定がある
- 直前にcodeを変更していない
6番目が重要です。codeを変更したなら、それはrerunではなく新しい検証です。前回の緑も前回の赤も、前回のSHAに対する結果として閉じます。
判断ロジックは擬似コードで固定します。
if CLASSIFICATION == DETERMINISTIC:
RERUN = FORBIDDEN
if CODE_CHANGED:
REQUIRED = FRESH_EXACT_HEAD_CI
PREVIOUS_GREEN = STALE
if CLASSIFICATION == UNKNOWN:
ESCALATE_TO_HUMANこのリポジトリには、対応する実装がすでに置いてあります。
- CIのconcurrency groupは、通常のpull_request実行とmanual rerunを分けています。rerunは元runの古いevent payloadを再利用するため、同じgroupへ入れるとstale SHAのrerunが最新SHAの実行をcancelできるためです。
Reject stale PR head rerunstepが、event側のPR head SHAとlive branch refをGit protocolで照合し、40桁で一致しなければ失敗させます。PR exact-head identityworkflowは、local checkout、event head、remote branch refの3経路でPR headの素性だけを独立確認します。scripts/ci/gh-api-retry.shは、rate limit由来のエラーだけを5秒、10秒、20秒、30秒で再試行します。401、404、422のような待っても変わらないエラーは1回で返し、枠が回復しない場合は最後の非0をそのまま返します。成功を偽装する経路は作りません。- typecheckのheap契約は
--max-old-space-size=4096として固定され、回帰テストがこの指定の消失を検出します。infrastructureに分類した失敗を、増量だけで閉じないための歯止めです。
分類器そのものを変更側から実行しない設計は#08が扱います。デプロイworkflowが緑になったことと本番が期待どおり動いていることを分ける設計は#10が扱います。
再利用できるチェックリスト
CIが赤くなったときに、上から順に通します。
- failure evidenceを、run_attemptとsubject_shaを含めて保存したか
- 可変部分を落としたfailure signatureを1行で書けるか
- そのsignatureが過去に何回出たかを数えたか
- 分類を1つ選び、根拠を書いたか
- deterministicなら、rerunではなくcode修正へ進んでいるか
- flakyなら、同一SHAで成功と失敗の両方を確認したうえで台帳へ登録し、修正課題を作ったか
- infrastructureなら、解析対象、対象ファイル数、重複repository、cacheを実測したか
- rate-limitなら、再試行が限定回数で、成功を偽装していないか
- unknownを、無理に他の分類へ寄せていないか
- code変更後に、fresh exact-head CIを回したか
- 検証したSHAが、いま操作しようとしている対象と一致するか
Evidence
| 内容 | 分類 | 根拠 |
|---|---|---|
| 2026-08-23、Actions installationのprimary rate limitでPR head identity確認が403停止し、Git protocolでの照合へ移した | OBSERVED | リポジトリの .github/workflows/pr-exact-head-ci.yml |
| 同日、95分で100 runが起動し39成功、60が403で失敗した | OBSERVED | リポジトリの scripts/ci/gh-api-retry.sh |
| rate limitだけを5、10、20、30秒で再試行し、401、404、422は再試行しない | IMPLEMENTED | リポジトリの scripts/ci/gh-api-retry.sh |
| typecheckは既定ヒープ超過でexit 134となるため4096MBを指定している | IMPLEMENTED | リポジトリの .github/workflows/ci.yml と package.json |
| heap指定の消失を回帰テストが検出する | IMPLEMENTED | リポジトリの scripts/agent-os/__tests__/ci-coverage.test.mjs |
| heapを8GBへ増やしてもOOMが再発し、原因は解析対象に入った重複worktreeだった | OBSERVED | リポジトリの docs/agent/LEARNINGS.md |
| manual rerunをrun_id単位へ隔離し、stale headのrerunを先頭guardで拒否する | IMPLEMENTED | リポジトリの .github/workflows/ci.yml |
| 分類の閾値と自動再試行の回数は、環境と規模に依存する | INFERRED | 上記実装の運用範囲からの推論 |
| failure signatureの自動抽出と分類の完全自動化 | PROPOSED | 現時点では人間が分類を選ぶ運用 |
適用限界
ここに書いた分類と手順は、Netsujoの運用目安であり、普遍的な正解ではありません。
- 再試行の待機量や回数は、job側のtimeoutと合わせて決めます。値をそのまま流用できません。
- ヒープの指定値はrunnerの搭載メモリに依存します。
- flakyを1回だけrerun可とする扱いは、テストの重要度によって変えます。安全に関わる検査では、flakyでもrerunを許可しない選択があります。
- 分類の効果を示す定量データは未計測です。失敗の再発率や、赤いまま放置された時間の変化は今後の計測対象です。
残るリスク
- 分類そのものを誤ると、誤った分類のまま自動化が進みます。特にinfrastructureとdeterministicの取り違えは、原因を残したまま緑を作ります。
- unknownを面倒に感じて他の分類へ寄せると、この仕組みは機能しません。unknownは残してよい分類です。
- 自動再試行は、rate limitの根本原因である呼び出し量を減らしません。
- fresh exact-head CIを回しても、base branchが進めば統合結果は別問題として残ります。
- 赤い実行のログは保持期間を過ぎると読めなくなります。evidenceの保存は実行時に行います。
まとめ
CIが落ちたときに最初に決めるのは、押すか押さないかではありません。この失敗はどの分類かです。
failure evidenceの保存
+
failure signatureの抽出
+
分類(deterministic / flaky / infrastructure / rate-limit / unknown)
+
分類ごとに許可された操作
+
code変更後のfresh exact-head CIこの順序を守ると、rerunは「祈り」ではなく、分類の結果として選ばれた操作になります。
よくある質問
rerunは禁止すべきですか
禁止ではありません。禁止するのは、原因を説明できないrerunです。rate limitのような一過性の外部要因は、限定回数の再試行で扱います。deterministicな失敗はrerunでは変わりません。
1ファイルをrevertしたら通りました。原因はそのファイルですか
その1回だけでは判定できません。OOMやrate limitのようなリソース制約由来の失敗は、総量が閾値を跨ぐかどうかで結果が変わります。差分とリソース設定の両方を動かして比較してから結論を出します。
signatureはどの粒度で作ればよいですか
そのsignatureを見て次の操作を選べる粒度にします。時刻、run id、一時パス、行番号のような可変部分を落とし、exit code、エラー種別を決める語、jobとstepの名前の3要素へ畳みます。
code修正後に、前回の緑を再利用できますか
再利用しません。前回の緑は前回のSHAに対する結果です。code変更後は、いま操作対象にしているexact headに対して新しくCIを回します。
flakyだと分かったテストは、どう扱いますか
1回だけのrerunで先へ進めることは許可しますが、そこで終わらせません。flakyとして台帳へ登録し、修正課題を作ります。登録しない運用では、同じテストが何度も1回ぶんのrerunを消費し続けます。
この記事の著者

飯田 友広
代表取締役
Netsujo株式会社 代表取締役。京都発のWeb3・AI実装スタートアップを2023年6月に創業。Webサイトを営業基盤として捉え、経営・営業・検索・生成AI・コンバージョン・計測を横断して課題と改善優先順位を整理する「Netsujo SIGNAL」を設計・運営。京都ビッグデータ活用プラットフォーム参画(小規模企業会員・ベンチャー)、同プラットフォーム発のワーキンググループ「Chain Up KYOTO」参画(2026-03-10)、IVS2026サイドイベント「なぜ京都でWeb3.0ビジネスなのか」を京都府庁旧議場で開催(2026-07-02・Netsujoとして主催、企画・登壇・運営/京都府デジタル政策推進課は共催)。京都美術工芸大学での講義・龍谷大学でのセミナー実績、ITコミュニティ「みやこでIT」(connpassメンバー614名・イベント167件・2019年2月から運営)運営。NPO法人NEMTUS理事、BAR KRYPTO運営。ソーシャル企業認証「S認証」認証企業(2026年2月認証・2026年4月公表)。技術領域はWeb3/ブロックチェーン/DID/NFT/生成AI/コミュニティ運営。
プロフィールを見るこの記事が向いている方
AIエージェントを開発へ組み込み、CIの赤が増えている開発責任者・ITエンジニア
rerunで緑になった理由を説明できないまま先へ進んでいるチーム
flakyとinfrastructureとdeterministicを同じ赤色で扱っている運用
— 壁打ち相談
読者のよくある相談
記事を読んだ後に「自分の状況だとどう判断すべきか」を整理するための壁打ち相談を受け付けています。下記のような相談例が当てはまる方は、お気軽にご連絡ください。
Q. CIが赤いが、rerunすると通ることがある
rerunの前にfailure evidenceを保存し、5分類のどれかを選んでから操作を決めます。
Q. 1ファイルをrevertしたら通ったので、それが原因だと判断してよいか
リソース制約由来の失敗では因果を誤ります。差分とリソース設定の両方を動かして比較します。
Q. code修正後、前回の緑を使い回してよいか
前回の緑は前回のSHAに対する結果です。exact headへ新しくCIを回します。
上記いずれかが該当する場合、初回30分の壁打ち相談で論点整理に対応します。記事に書ききれない個別事情を踏まえた判断材料が必要な段階こそ、壁打ちが活きやすいフェーズです。