複数のAIエージェントが同じリポジトリで働くためのgit衛生ルール
この記事について
このスタジオでは現在、Claude Code・Codex・Antigravity・Gemini・Gemma4 の5種のAIエージェントが同じgitリポジトリを横断して作業している。それぞれがブランチを切り、worktree を作り、ファイルをコミットする。人間が1人でgit管理している状態とは性質が異なる問題が出てくる。
2026年6月に実害が出たため、運用ルールと自動掃除スクリプトを整備した。この記事はその記録。
何が起きたか(2026-06-05 の事故)
古いブランチ(main から数十コミット遅れた状態)で作業が続いていたセッションが、そのブランチから rsync で perf 環境サーバーに直接デプロイした。その結果、main にはすでにマージされていた改善が本番環境から消えた。
原因はシンプルだった。
- worktree を使い捨てにしないまま数日放置していた
git statusはローカルの変更を正しく見せるが、main との乖離は示さない- エージェントが「ローカルが最新」と判断してデプロイした
この事故を起点に、複数エージェント環境での git 運用ルールを明文化することにした。
整備したルールセット
ブランチの扱い
| 種別 | ルール |
|---|---|
| main への直接 push | 禁止。必ずブランチを切って PR 経由でマージ |
| worktree | 「1セッション = 1タスク = 使い捨て」。終わったら削除 |
| stale ブランチ | 週1で確認。継続するなら main に rebase、捨てるなら gh pr close してブランチ削除 |
| デプロイ元 | 必ず main にマージ後、対象機で git pull して取得。古いブランチから rsync しない |
ブランチ命名規則
エージェントが自動生成するブランチは claude/ プレフィックスで識別する(例: claude/festive-cerf-b53482)。内容を表す名前に変えたい場合は feat/ 等に切り直す。
feat/ — 新機能・新記事
fix/ — バグ修正
chore/ — メンテ・依存更新
docs/ — ドキュメント
perf/ — パフォーマンス改善
claude/ — エージェント自動生成(worktree 名)
未追跡ファイルの扱い
複数エージェントが main で直接作業すると、コミットされていないファイルが main に溜まる。原稿ファイル(まとめ媒体の drafts 等)がこの状態になりやすい。
作業前に未追跡数を確認する:
git status --short | grep '^??' | wc -l
未追跡が多い場合は、コミットするか .gitignore に追加するかを決めてから作業を始める。
セッション終了時のチェックリスト
1. 変更をコミット・push
2. PR を作成してマージ(またはユーザーに確認)
3. worktree を削除: git worktree remove --force .claude/worktrees/<name>
4. ローカルブランチを削除: git branch -d <name>
3 の --force は未コミットの変更を確認なしで捨てる。1〜2(コミット・push・PR)を済ませてから打つこと。途中状態を残したい場合は git stash または WIP: prefix でコミットして push する。worktree に dirty な状態を残したまま放置しない。
remove に --force が要る理由(2026-08-27 訂正)
初版はチェックリストの 3 を git worktree remove .claude/worktrees/<name> と書いていた。このリポジトリでは、このコマンドは初版公開の時点から必ず失敗する。
$ git worktree remove .claude/worktrees/<name>
fatal: working trees containing submodules cannot be moved or removed
リポジトリ内に submodule 参照が1つでもあると、その submodule を使わないサイトを触っている worktree も「submodule を含む worktree」として扱われる。素の remove は特定の worktree で落ちるのではなく、全 worktree で落ちる。
git-worktree(1) は remove の項で、--force を付ければ消せると明記している。
Unclean worktrees or ones with submodules can be removed with
--force.
moved or removed と2つの操作を同時に名指しするため、「submodule を含む worktree は移動も削除もできない」と読める。実際に不可能なのは move のほうだけで、同じ man page は move の項に「submodule を含む worktree は移動できない」と単独で書いている。move の --force が効くのは worktree が locked の場合と移動先が欠けている場合であって、submodule には効かない。--force で通るのは remove だけ。
git 2.40.0 で確認した。submodule 展開済み・worktree が dirty(未追跡ファイルと追跡ファイルの変更の両方)・submodule 側が dirty・自分がその worktree の中に居る、の4条件を同時に満たした状態でも --force は成功する(exit 0、ディレクトリと worktree list の登録の両方が消える)。
エラー文が2つの操作を同時に名指ししているときは、片方だけが不可能である可能性を疑う。man page を引くコストは一度きりで、誤読したまま運用するコストは worktree を畳むたびにかかる。
自動掃除スクリプト(scripts/git_cleanup.sh)
手動でのブランチ・worktree 管理を補うため、scripts/git_cleanup.sh を整備した。
このスクリプトが消す条件:
- worktree: ディレクトリが消えている(prune可) / またはブランチが origin/main にマージ済みかつ未コミット変更がない
- ブランチ: origin/main に完全マージ済み(
git cherry origin/main $bで独自コミット 0)かつどの worktree にもチェックアウトされていない
main と現在チェックアウト中のブランチには絶対に触らない設計になっている。
使い方:
# dry-run(何が消えるか表示するだけ。デフォルト)
bash scripts/git_cleanup.sh
# 実際に削除
bash scripts/git_cleanup.sh --apply
マージ済み判定は git cherry origin/main $b | grep -c '^+' で行っている。+ で始まる行はorigin/mainにない独自コミットを意味するので、これが0件ならマージ済みと見なせる。
定期実行(週次)を launchd に登録することで、溜まる前に刈れる。
やってわかったこと
「作業前 fetch」は省略できない。 git status はローカルの整合性を見るが、リモートとの乖離は見ない。デプロイ系の判断をする前には必ず git fetch origin main && git log HEAD..origin/main --oneline でリモートの先行を確認する必要がある。
worktree は「あって当然」ではない。 エージェントが自動でworktreeを作り、自動で消す設計が成立して初めて「放置」が減る。手動削除に頼る設計はエージェント増加に比例して技術的負債を積む。
ルールの明文化は事故の後でよい(が、早ければ早いほどよい)。 今回は git_cleanup.sh のコメントに事故の経緯を書いた。なぜこのスクリプトが存在するかの文脈が残っていれば、数ヶ月後に読んだときに意図を誤解しない。
更新履歴
- 2026-07-23: 初版公開
- 2026-08-27: セッション終了時のチェックリストの worktree 削除コマンドを訂正。取り違えの原因を節として追加。
訂正履歴
- 2026-08-27: 「セッション終了時のチェックリスト」の 3 に書いていた
git worktree remove .claude/worktrees/<name>は、submodule を参照しているこのリポジトリでは初版公開の時点から実行できないコマンドだった(fatal: working trees containing submodules cannot be moved or removedで失敗する)。--forceを付ける形に訂正し、未コミットの変更を捨てる副作用の但し書きを添えた。