PRにならないまま消えかける作業を未pushブランチ検出で見張る
今回やったこと
AIエージェントが worktree で作業する運用では、変更が作業ブランチの中に閉じたまま、PRにもならず、mainにも届かない状態が起こる。今回の対象は、すでにPRになった作業の滞留ではない。そもそも一度も push されず、GitHub側から見えないブランチを見張るための番犬を追加した。
きっかけは、Issue が参照していたドキュメントとサイトの実体が origin/main に無く、未 push のローカルブランチに閉じ込められていた調査だった。該当ブランチの具体的な名前や分岐点のコミットは、公開記事では省略する。PRは1件も作られていなかった。同様の事例がないか調べると、別の未 push ブランチにも記事や学習ノート、スクリプトが残っていた。
「未 push」だけでは判定しない
最初に考えやすい判定は、ローカルブランチが origin に存在しないことだ。しかし、それだけでは誤検知が多い。別ブランチで同じ変更が取り込まれた場合や、手コピーで成果物だけが main に届いた場合、ブランチ自体が未 push でも実害は残っていないからだ。
そこで判定を二段に分けた。
| 判定 | 見るもの | 意味 |
|---|---|---|
| 1 | origin に同名ブランチが無い | 一度も push されていない、またはリモート側で削除された可能性 |
| 2 | ブランチが触ったファイルの中身を main と比較 | 成果が main に届いていない可能性 |
2段目では、ファイルが main に無い absent と、存在するが内容が違う differs を分ける。追加されたファイルだけを数えると、既存ファイルの変更を落としてしまう。実際、追加だけを対象にした棚卸しでは約20本だったものが、差分全体を見ると約40本になった。既存ファイルの改修のみで構成されるブランチが「実害なし」と誤分類されていたため、追加・削除・変更をまとめて確認する設計へ改めた。
存在判定にも注意が要る。git rev-parse <rev>:<path> はパスごとに終了コードを扱う必要があり、大量のパスを調べるには非効率である。そのため、存在確認は一括判定できる cat-file --batch-check の missing を使う。監視の精度は、比較ロジック以前に「存在しない」を正しく表現できるかで決まる。
監視対象から外すものを先に決める
番犬は、見つけたものをすべて異常として通知する仕組みではない。稼働中の worktree は、既定では1日以内に触られているものを対象外にする。作業途中のブランチを「消えた成果」として鳴らせば、通知の信頼性が落ちるからだ。
画像や動画など raw/ 配下のメディアも除外する。これらは Git に入れない運用が正常で、正本は別の保管先へ退避する。反対に prompts.json のようなサイドカー JSON は追跡対象として残す。.gitignore で無視されている生成物も、Gitに聞いて除外する。除外規則をスクリプトに重複して持つと、リポジトリ側のルールが増えたときに監視だけ古くなるためだ。
通知先も「今日増えた異常」と「積み残しの棚卸し」に分ける。前回から増えたブランチは異常として通知し、初回の全件や一定期間ごとの再掲はログへ回す。古い在庫を毎回「今日起きたこと」として送ると、本当に新しい異常が埋もれる。
やってみてわかったこと
未 push ブランチの監視は、GitHub APIだけでは完結しない。origin に存在しないブランチは、PR一覧にも出ないため、ブランチを切った機械のローカル refs を見る必要がある。また、検出した約40本を自動で救出する仕組みにしてはいけない。古いだけのブランチを戻すと退行する可能性があり、differs が未マージ編集なのか、mainの進行による陳腐化なのかは中身を読んで仕分ける必要がある。
実装済みの番犬がするのは、候補を決定的に並べ、分岐点から main がどれだけ進んだかを添えて、人間が確認できる状態にするところまでだ。救出・破棄・公開は別の判断として残す。無人運用で重要なのは、成果を自動的に動かすことではなく、閉じこもった成果を見える場所へ出すことでもある。
実装ステータスと更新履歴
実装済み・稼働中。参照した実装は、孤立ブランチの監視スクリプトとテストコードである。
- 更新履歴: 2026-09-12 初稿。
- 訂正履歴: なし。