◉ANTIGRAVITY LABEN
記事一覧/アプリ開発
▣ アプリ開発/2026-11-05中級

「cannot pull with rebase: You have unstaged changes」で止まった朝。autostash で通したら、終了コード 0 のまま衝突が残りました

エージェントに触らせたリポジトリで git pull --rebase が unstaged changes で止まったときの切り分け順と、--autostash が衝突しても終了コード 0 を返す点を手元で再現して確かめた記録です。

git17pull-rebaseautostashエージェント運用21トラブルシューティング46

エージェントに更新作業を任せた翌朝、手元で git pull --rebase origin main を打つと、次の二行で止まりました。

error: cannot pull with rebase: You have unstaged changes.
error: please commit or stash them.

個人開発でアプリの更新を回していると、このメッセージには何度も出会います。前日のセッションが途中で終わり、作業ツリーに未コミットの変更が残っているのです。

最初にお伝えしたいのは、このエラーが「壊れた」ではなく「守ってくれた」という合図だという点です。ただ、守りを外すために私が最初に覚えた近道には、小さな落とし穴がありました。手元の git 2.34.1 で再現して確かめた内容を、順番に書き残します。

まず「何が残っているか」を見る

取り込みの前に確かめることは一つだけです。

git status --short --untracked-files=no

追跡済みファイルの変更だけを出します。 M notes.md のように出れば、それが止めている犯人です。未追跡ファイルは pull --rebase を止めないので、ここでは数えません。

見たあとの分かれ道は三つあります。

残っていたもの最初の一手理由
エージェントの作業の続きで、残したい変更先にコミットする履歴に残るので、あとで取りこぼしません
試しに触っただけの変更git restore で戻す取り込む対象を最新の状態だけにできます
判断がつかない変更git stash で退避する消さずに、取り込みだけ先に進められます

私は三つ目の「判断がつかない」ときに、もっとも手が止まります。そこで登場するのが --autostash です。

--autostash は便利で、少しだけ危うい

--autostash を付けると、git が退避から取り込み、復元までを一度にやってくれます。

git pull --rebase --autostash origin main

リモートと重ならないファイルだけが汚れていた場合は、次のように何事もなく終わりました。

Created autostash: cc3d9ae
Fast-forward
 config.txt | 1 +
Applied autostash.

問題は、リモートで変わったファイルと、手元で残っていた変更が同じファイルに重なったときです。同じ検証用リポジトリで、notes.md の同じ場所をリモートと手元の両方で変えて試しました。

Created autostash: cc3d9ae
Fast-forward
 notes.md | 1 +
Applying autostash resulted in conflicts.
Your changes are safe in the stash.
You can run "git stash pop" or "git stash drop" at any time.

この直後に echo $? を打つと、0 が返ってきました。

そして notes.md の中身には、衝突マーカーが入っています。

n
<<<<<<< Updated upstream
remote note
=======
agent leftover
>>>>>>> Stashed changes

取り込みそのものは成功しているので、終了コードは 0 です。衝突したのは「退避した変更を戻す」段階だった、というわけです。変更が失われたわけではなく、stash@{0} にも残っています。ただ、スクリプトやエージェントに && でつなげてビルドや push を任せていると、衝突マーカー入りのファイルがそのまま先へ進んでしまいます。

予想と逆だったのはここでした。いま思えば、終了コードを疑う癖がなかったのです。「autostash を付ければ安全」と思い込んでいた私は、終了コードを信じて次の工程を走らせる書き方をしていたのです。

失敗の形を二つに分ける

ここまでを踏まえて、私は取り込みを二つの書き方に分けています。

一つ目は、汚れていたら止める書き方です。無人で回す手順には、こちらを標準にしています。

#!/bin/bash
# 取り込み前に「作業ツリーが汚れていないか」を先に見て、汚れていれば理由つきで止める
set -euo pipefail
branch="${1:-main}"
 
dirty="$(git status --porcelain --untracked-files=no)"
if [ -n "$dirty" ]; then
  echo "未コミットの変更が残っています。取り込みを中止します:" >&2
  echo "$dirty" >&2
  exit 2
fi
 
git pull --rebase origin "$branch"
echo "sync ok: $(git rev-parse --short HEAD)"

汚れた状態で実行すると、次のように終了コード 2 で止まります。

未コミットの変更が残っています。取り込みを中止します:
 M notes.md
exit=2

「何が残っているのか」がログに出るので、翌朝に原因を探し回らずに済みます。

二つ目は、人が手元で使う、autostash を許す書き方です。戻したあとの衝突を、終了コードとは別に自分で確かめます。

#!/bin/bash
# autostash を使う場合は、戻した後の衝突を自分で確かめる
set -euo pipefail
branch="${1:-main}"
 
git pull --rebase --autostash origin "$branch"
 
conflicts="$(git diff --name-only --diff-filter=U)"
if [ -n "$conflicts" ]; then
  echo "退避した変更を戻す際に衝突しました(変更は stash に残っています):" >&2
  echo "$conflicts" >&2
  exit 3
fi
echo "sync ok: $(git rev-parse --short HEAD)"

先ほどの衝突ケースで動かすと、notes.md を名指しして終了コード 3 で止まりました。git diff --diff-filter=U は未解決(unmerged)のファイルだけを拾うので、衝突がなければ空になり、そのまま通ります。

なぜ二つに分けるのか。無人の手順は「止まることが安全」で、人が見ている作業は「進めたうえで直せる」ほうが速いからです。無人の手順では汚れを許さずに止め、人が見ている手順では許したうえで戻し口を確かめます。この線引きが、いまの私の答えなのだと思います。

衝突したあとの片づけ

autostash で衝突してしまった場合の片づけも、検証用リポジトリで通しておきました。

  1. git status で UU と出ているファイルを開き、マーカーのどちらを残すか決めて保存します
  2. git add <ファイル> で解決済みにします
  3. git stash drop で、役目を終えた退避を捨てます

三番目を忘れると、git stash list に autostash が残り続けます。私も検証中、前の実験の退避が残ったままで次の実験を始めてしまい、一覧に見覚えのない行が並んで首をかしげました。取り込みの前後に git stash list を一度見る癖をつけておくと、この混乱は防げます。

直らないときの次の一手

ここまでやっても止まるときは、次の二点を疑います。

  • リベース途中の状態が残っている: git status の先頭に rebase in progress と出ます。git rebase --continue か git rebase --abort で、先にその途中を終わらせます
  • 別のブランチにいる: git branch --show-current を見ます。エージェントが作業用ブランチに切り替えたまま、元に戻していないことがあります

いずれも、メッセージが「unstaged changes」ではなく別の文言で出るので、読み分けは難しくありません。

今日の持ち帰り

まず git status --short --untracked-files=no を打って、止めているものの正体を見てください。そのうえで、無人で回す手順には上の一つ目のスクリプトを、手元の作業には二つ目を使い分けていただければ幸いです。

「終了コード 0」を、そのまま「きれいに終わった」とは読まないようにしています。この原則だけは、エージェントに手順を任せるようになってから、いっそう強く意識するようになりました。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

▣ アプリ開発2026-10-30
画像の拡大表示を Esc で閉じたら、背面のスクロールが戻らなくなりました。dialog 要素に載せ替えて数えたこと
作品の拡大表示を直したとき、Esc で閉じない・背面が動く・閉じるボタンが画像に重なる、の三つを順に潰しました。dialog 要素へ載せ替えても残った「Esc のあとスクロールが戻らない」不具合まで、確認スクリプトと数字で書き残します。
▣ アプリ開発2026-10-28
サイトマップの 155 URL を全部取りに行きました。全件 200 でしたが、lastmod の 93% が同じ日付でした
サイトマップの全 URL を実際に取得し、ステータス・canonical・hreflang・lastmod を突き合わせた記録です。標準ライブラリだけの Python 全文と、見つかったずれを載せました。
▣ アプリ開発2026-10-22
言語ごとの文言がいつのまにかずれていた。エージェントに点検を頼む前に走らせる、60 行の strings.xml チェック
多言語の Android アプリで、キーの欠落・空文字・プレースホルダ不一致を機械で見つける Python スクリプトと、その結果だけを Antigravity に渡して修正案を出させる運用の手順をまとめました。