Cursor を複数走らせると git stash が勝手に増え続ける — 並行 AI エージェントの working tree 事故と中間コミット運用
AI 主体で執筆事象:気付いたら `stash list` に覚えのない退避が溜まっている
1 日のうちで、同じリポジトリに対して複数の Cursor セッション(または Cursor + Claude Code + IDE の git ビュー)を同時に走らせる日がある。個人開発でも、記事を書きながら別ブランチで実装を進めたり、AI にリファクタを投げて待っている間に別タスクを触ったりすると普通にそうなる。
ある日、ルール更新を main で作業していたら、気付いたときには別ブランチに切り替わっていた。元の変更は消えたように見えたが、git stash list を叩くと知らない stash が積まれていた。
$ git stash list
stash@{0}: WIP on main: 4ff4de7 Workflow: define commit message prefix convention
stash@{1}: WIP on feat/28-ai-authored-blog-disclosure-design: ...
stash@{2}: WIP on main: ...
stash@{3}: WIP on main: ...
これが 1 日の間に何度か発生し、そのたびに「どの stash が最新なのか」「どれを apply すれば作業が戻るのか」で時間を溶かした。Cursor Rules を 3 ファイル触って 20 分ほどの作業だったのに、復旧に同じくらい時間がかかると、中間コミットを打っていなかった事実が重く感じる。
発生条件を自分の経験範囲で整理すると、次のどれかに該当するときに起きていました。
- 片方のセッションで編集中、もう片方のセッションがブランチを切り替えた(ワンクリックの checkout で working tree が衝突し、片方の変更が退避される)
- IDE の AI エージェントがバックグラウンドで git 操作(pull, checkout, rebase)を実行し、その都度 working tree を clean にするために stash を使った
- 1 つの AI エージェントが自律的にブランチ運用を進める設計になっていて、別のエージェントの未コミット作業に気付かないまま checkout した
いずれも悪意ではなく、AI エージェント側は「clean な working tree にしてから作業を進める」正しい振る舞いをしているだけです。前提として「未コミットの変更は誰かが責任を持って書き切る」という暗黙のルールがあったのですが、並行走行ではその暗黙が崩れます。
復旧の基本:stash を 3 つの視点で見る
事故が起きたときの戻し方から書いておく。やり方を知っていれば 5 分で済む作業が、知らないと 30 分になる。
1. git stash list で全体を把握
まずは棚卸し。各 stash の meta 情報からどれが自分の作業か推測する。
$ git stash list
stash@{0}: WIP on main: 4ff4de7 Workflow: define commit message prefix convention
stash@{1}: WIP on feat/28-...: ...
「どのブランチで積まれたか」と「そのブランチの HEAD コミット」がわかる。自分がどのブランチで何をしていたかの記憶と照合する。
2. git stash show -p stash@{N} で中身を確認
疑わしい stash の diff を見て、自分の作業か判断する。
$ git stash show -p stash@{0}
diff --git a/.cursor/rules/workflow.mdc b/.cursor/rules/workflow.mdc
...
ファイルパスとキーワードを眺めれば、自分が書いていた作業かどうかはすぐわかる。
3. apply で戻す(pop は使わない)
戻すときは pop ではなく apply を使う。
$ git stash apply stash@{0}
apply は stash を残したまま適用する。pop は成功すると自動で stash を削除するが、apply 中に conflict が起きたら自動削除が止まる――という挙動の揺れが、並行走行で既にカオスな作業を余計にわかりにくくする。「戻すときは常に apply、確認してから手動で drop」を原則にすると予測可能性が上がる。
apply でコンフリクトが出たら、通常のマージ解消と同じように git status → 編集 → git add。最後に不要な stash を消す。
$ git stash drop stash@{0}
メモ:stash が 4 つ以上溜まった時点で「どれが何か」を後から復元するコストは跳ね上がる。溜める前に中間コミットに切り替える方向に運用を寄せたほうが安い。
予防策:3 層で止める
復旧できるのと、そもそも起きないのは別の話なので、自分なりに効いた予防策を 3 層に整理しました。効果の大きい層から順に入れていけばよいはずです。
| 層 | 対象 | やること |
|---|---|---|
| L1 | 作業の習慣 | 長時間タスクは中間コミットを打つ |
| L2 | ブランチ運用 | `main` で編集を始めない、セッションごとにブランチを明示 |
| L3 | Git 設定 | `rerere.enabled` でコンフリクト解消を再利用可能に |
L1:5 ファイル or 30 分で WIP コミット
一番効いたのはこれ。本ブログのワークフロールールに次の条文を入れた。
# .cursor/rules/workflow.mdc(抜粋)
## 長時間タスクでの中間コミット
以下のいずれかに該当する作業は、キリのいい単位で中間コミットを打つ。
- HTML/CSS/設定ファイル等を 5 ファイル以上触る作業
- 同じセッション内で 30 分以上跨ぐ作業
- 複数ブランチを行き来する可能性がある作業
中間コミットのメッセージは `WIP: {概要}` で可。PR を開く前に squash / rebase で整理する。
目安は「5 ファイル or 30 分」と数値で決めてしまうのが大事で、「キリのいいところで」みたいな曖昧な基準だと人間も AI も守りません。どこで打てばいいかを数値で明示すると、AI エージェント側も自律的にコミットを挟みに来るようになります。
WIP コミットが増えて汚れることを心配する声はあるが、本ブログではそもそも squash merge を積極的に使わず、推敲系のコミットはそのまま残す方針なので衝突しにくい。文言推敲系のコミット(Refine / Tighten / Polish)は squash 対象から外す、という例外を同じルールに併記してある。
L2:`main` で編集を始めない
並行セッションで main を触るのは事故の温床で、小さな修正に見えても、もう片方のセッションがバックグラウンドで git pull や git checkout をする可能性があります。
運用ルールとして次を置いた。
- どんな小さな作業でも、まずブランチを切る(
fix/,docs/,chore/でも構わない) - 各 Cursor セッションは開始時に自分が立っているブランチを宣言(`git branch --show-current` をログに出す)
- セッションを閉じるときに working tree が dirty なら、WIP でもコミットしてから終わる
とくに「閉じるときにコミット」は効く。stash と違って、コミットされているものは別セッションが checkout してきても何もできない(conflict で止まる)ので、勝手に退避されない。
L3:`rerere` で同じコンフリクトを覚えさせる
並行走行では同じ場所でのコンフリクトが繰り返し起きます。典型的なのは blog/index.html の記事一覧や backlog/ideas.md のような、複数ブランチから同じ行を触るファイルです。
$ git config rerere.enabled true
$ git config rerere.autoupdate true
rerere(reuse recorded resolution)を有効にすると、1 度解消したコンフリクトの「解消パターン」を Git が記憶し、次に同じ衝突が出たときに自動適用してくれる。Pro Git book に詳しいが、個人開発だと「この設定を知らないまま」の人が多い。
ただし、rerere は「同じ場所を同じように解消する」前提なので、AI 生成コードのように解消結果がブレる場合は、自動適用の結果を必ず目視確認するようにしてください。無条件の万能薬ではありません。
根本解決の方向:エージェントに「状態宣言」を強制する
予防策を詰んでも、根本的には「複数のエージェントが同じ working tree を共有している」構造が問題で、本当の解は 2 方向あり得ます。
方向 A:エージェントごとに worktree を分ける
Git の worktree 機能を使うと、同じリポジトリに対して複数のチェックアウトディレクトリを持てる。
$ git worktree add ../60d.dev-agent-b feat/30-stash-article
$ git worktree add ../60d.dev-agent-c feat/31-yaml-autogen
各エージェントが別ディレクトリで動けば、working tree を踏み合うことは構造的に起こらない。ブランチも自然に分かれる。
欠点は、ディスク容量(node_modules などは重複する)と、エージェントの「プロジェクトルート認識」の設定が増えること。Cursor だとプロジェクトごとに rules が読まれるので、worktree 間でルールを共有したいときに工夫が要る。それでも、stash 事故の根絶を考えると採用する価値は十分ある。
方向 B:エージェントに「ブランチ切替前に状態宣言」を強制
エージェントが git checkout や git switch を実行する前に、以下を自分で報告させる運用ルールを書く。
- 現在のブランチ名
git statusの結果(dirty / clean)- dirty な場合、自分の作業かどうかの判断と、切り替える前にコミットすべきかの提案
ルールとしてはシンプルですが、効きます。AI が prompt 側で「working tree を確認して、dirty なら理由を述べてから進む」と思考するようになります。これまで使ってきた感触では、この 1 文を workflow.mdc に置くだけで、事故の頻度は体感で数分の 1 になっています。
根本解決とまでは言わないが、worktree 化に踏み切る前の中間解としては十分コスパがいい。
まとめ
- 複数の AI エージェント(Cursor / Claude Code / IDE 内 AI)を同じリポジトリに並行で走らせると、ブランチ切替に連動した自動 stash が起きて作業が飛ぶ
- 復旧の基本は
git stash list→stash show -p→apply(popではなく)。drop は手動でやる - 予防は 3 層:(L1) 5 ファイル or 30 分で WIP コミット、(L2) `main` で編集しない・セッションごとにブランチ宣言、(L3) `rerere` で解消パターンを記憶
- 根本解決の方向は (A) `git worktree` でエージェントごとに作業ディレクトリを分ける、(B) エージェントに「ブランチ切替前の状態宣言」を強制。(B) は 1 文ルール追加で済むので最初に試す価値が高い
並行 AI エージェントは、ソロ開発者の生産性を数倍に引き上げる一方で、従来の「単一開発者・単一 working tree」を前提とした Git の挙動と摩擦を起こす。発生する事故は個別には些細だが、積み重なると 1 日のうちの無視できない時間を食う。「起きるものとして予防と復旧を設計しておく」段階にきている。