CI待ちで止めたPRを、同じ変更のまま拾い直す
今回やったこと
CIが一定時間内に緑にならないとき、自動マージを止めるゲートに「停止後の回収経路」を加えた設計を整理する。対象は scripts/require_ci_green.py と scripts/lib/gate_retry.py、および15分ごとに動く自動マージdaemonである。2026年9月15日の記録では、ゲートがPRを残した後にCIが緑になっても、そのPRを確認する担当がなく、#4085、#4121、#3953が未処理で残っていた。
実装ステータス
実装済み。ここではリポジトリ内の設計コメントとコードに記された動作を説明する。GitHub上で現在稼働している状態を独立に確認したものではない。
「止める」だけでは終わらない
従来のゲートは require_ci_green.py <PR> --wait 600 で最大600秒待ち、緑にならなければマージせずPRを残して通知する。停止は安全側の判断だが、次の定期実行は新しいPRを作るだけで、残ったPRを見直す仕組みはなかった。通知が一度出ても、その後の担当が決まっていなければ、時間が経ってCIが緑になったPRも動かない。
この穴を埋めるため、停止時に2つの記録を残す。まずPRコメントに停止時点のhead SHAと時刻を記録し、次に ntm:gate-retry ラベルを付ける。順序にも意味がある。コメントを先に書けば、記録に失敗したのにラベルだけ残り、拾う側がどの変更を検査すべきか分からなくなる状態を避けられる。コメントだけ残ってラベルが付かない場合はdaemonが拾わないため、誤った自動処理には進まない。
CI待ちが時間切れ
→ head SHAをコメントに記録
→ ntm:gate-retry を付与
→ daemonが15分ごとに再確認
再試行で守るのは「同じhead」
daemonはラベル付きPRのCIを再確認し、記録されたheadと一致する場合だけ、そのheadを指定してマージする。単にPR番号が同じだから続けるのではない。待っている間に誰かがpushしてheadが変わった場合、ゲートが検査していない変更まで自動で通すことになるため、人へ返してラベルを外す。
例外はGitHubによるbase取り込みである。mainの更新を取り込むとhead SHAは変わるが、変更内容が増えたとは限らない。コードは親コミットの関係に加え、GitHubが作成した署名済みコミットかどうかも確かめる。手元で同じ形のmerge commitを作れても、そこに未検証の変更を含められるからだ。確認できた場合だけ、元の検証済みheadからbaseを取り込んだ更新として追跡を続ける。
期限と人への引き渡し
再確認の結果ごとに扱いを分ける。CIが緑でheadが保たれていれば固定したheadをマージする。head変更は人へ返し、mainとの衝突は閉じる。CIが赤なら24時間に一度通知し、未完了なら次の周回を待つ。48時間たっても緑にならないPRは閉じて通知する。時間制限によって、止めたPRが無期限に残る状態を避ける。
| 状態 | 処理 |
|---|---|
| 記録したheadでCIが緑 | そのheadに固定してマージ |
| 誰かのpushでheadが変化 | 自動処理をやめて人へ返す |
| mainとの衝突 | PRを閉じる |
| CIが赤 | 24時間ごとに通知 |
| 48時間たっても緑にならない | PRを閉じて通知 |
| CIが未完了 | 次の周回で再確認 |
この方式で大切なのは、再試行を単純な「もう一度マージ」ではなく、停止した判断を同じ入力について再評価する処理として扱うことだ。ゲートが確認したheadと違う変更が入れば、自動の権限もそこで終わる。
運用上の境界
再試行が使う権限は新しく作らない。マージを試みてCI待ちだけで止まったPRに限って印を付ける。さらに GATE_RETRY_AUTOMERGE=off で回収全体を止められ、48時間の期限も設定されている。一方、拾い直し後のマージでは、通常のdrip後処理(デプロイ、着地確認、IndexNow)は実行されないという制約がコード内に記録されている。後処理を含むパイプライン全体が再実行されると誤解せず、別の確認経路が必要な点を把握しておく。
CIゲートを置くなら、失敗時に止まることだけでなく、止めた仕事を誰がどの条件で再開するかまで決める必要がある。同じheadを守り、変更があれば人に返し、一定時間で打ち切る。これが自動化における停止と再開の境界になる。
更新履歴
- 2026-09-28: 初稿を作成。