cd /news/developer-tools/git-said-it-succeeded-the-state-said… · home topics developer-tools article
[ARTICLE · art-115482] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Git Said It Succeeded. The State Said Otherwise.

A developer discovered several misleading success messages in Git, including cases where rerere fails to record resolutions after an abort, autoupdate hides conflict checkpoints, and --autosquash can report success while leaving fixup commits behind. The findings, based on tests with Git 2.39.5, highlight the importance of verifying actual repository state rather than trusting command output.

read6 min views1 publishedAug 30, 2026

I keep finding git behaviours the same way: set up the case, run the command, then check the state I actually cared about instead of trusting the success message. Guides pair rerere.enabled

with rerere.autoupdate

when the second one removes the checkpoint the first one earns you. GitHub reports mergeable while the reviewer is still reading stale bytes. --autosquash

prints Successfully rebased and updated

and leaves a fixup!

commit sitting in history.

Copy the config. The reasoning is why you would leave one of them off.

This started as a comment on Sylwia Laskowska's git list. She asked me to turn it into a post. The first four items are from ordinary rebasing. The rest I measured this week on a stacked-PR repo.

rerere

does not learn from git add

alone

git config rerere.enabled true

rerere

is reuse recorded resolution. It remembers how you resolved a conflict and replays it when the same one appears. On a long rebase that is resolving something once instead of five times.

CONFLICT (content): Merge conflict in f.txt
Staged 'f.txt' using previous resolution.

Resolve, git add, then abort immediately, and it recorded nothing. The next attempt conflicts identically. That bit me twice before I understood it.

Two things are going on and my test could not separate them, so here are both. git add

does not record the postimage — git commit

, git rebase --continue

, or an explicit git rerere

do. And git rebase --abort

runs rerere clear

, which wipes the in-flight metadata anyway.

What I could measure, on git 2.39.5: resolve + git add

  • abort, and the next attempt conflicts identically. Resolve + git add

  • explicit git rerere

  • abort, and it replays — before the rebase goes anywhere. So "it learns when the rebase finishes" is the wrong model either way.

I taught it a resolution in one.txt

. Then I created the same conflicting hunk in a completely different file, two.txt

, on a different branch. It replayed the one.txt answer into two.txt.

On a rebase that is usually what you want. It is not always what you want. If the right answer differs between those two places, it will quietly give you the first one.

rerere.autoupdate

I kept seeing them paired. I checked the index in both modes:

autoupdate off  ->  UU two.txt    still conflicted, you must read it and git add
autoupdate on   ->  M  two.txt    fully staged, nothing asks you to look

Autoupdate off is the checkpoint that catches a replay you did not want. Leave it off and rerere

still writes the resolution into the file. You just have to look at it before adding.

If it got it wrong:

git rerere forget <path>   # drop what it learned
git checkout -m <path>     # bring the conflict markers back

--autosquash

can succeed and still leave the fixup in history

git commit --fixup abc1234
git rebase -i --autosquash abc1234~1

First marks it. Second reorders and squashes it. The todo list opens already correct.

Two sharp edges.

Bare git rebase -i --autosquash

with no range fails outright on a branch with no upstream, so you need the base.

And if the range does not reach far enough back to include the target, it does not warn you. It prints Successfully rebased and updated

and leaves the fixup!

commit sitting in your history.

git log --oneline | grep fixup!

Worth running after. The rebase step rewrites historygit commit --fixup

just adds a commit — so the usual rule about rebasing shared commits applies to the second line, not the first.

This one I did not set up on purpose. I had a stack: main ← #3 ← #4 ← #2

. After #3 and #4 merged, I retargeted #2 onto main

. GitHub immediately showed base: main

, mergeable, clean.

The new base commit was still not an ancestor of my head.

I measured it. Head b1cf109

versus main

at e6dc136

: GitHub compare status diverged

, behind by 8. After git merge origin/main

(commit b4ac271

): behind by 0.

git fetch origin main
git merge-base --is-ancestor origin/main HEAD && echo "head contains main" || echo "STALE — merge first"

Exit 1 is the stale case. GitHub's "Change base" button is not "Update branch." Change base retargets what the PR is compared against. Update branch, or git merge origin/main

, is what actually brings the commits in.

Those SHAs are from a private stack, so this one is a field note rather than something you can clone and rerun. The check itself is two lines and works anywhere.

That head was what the automated reviewer had to work from. It carried an old implementation I had already replaced on main

, and I burned a cycle chasing a finding about code that only still existed on that stale branch — before I thought to check whether the branch contained what I thought it contained.

The fix is one merge, then the ancestor check. MERGEABLE

does not mean "this head contains current main."

main

A link to /blob/main/path/file

shows whatever main

says today, and 404s outright if the path later moves. A link to /blob/<sha>/path/file

is immutable. Both may return 200 right now. Only one of them still means the same thing next month.

I hit this the same week. main

still carried an old write-up while the corrected file only existed on a branch. I published the SHA-pinned URL so a reader could open the bytes I was citing, not whatever landed on default later.

If you are citing evidence, cite the sha.

git worktree

instead of stashing, if you have untracked files you cannot lose Stash does not save untracked files unless you remember -u

. I had an untracked scratch file I needed to keep through a week of branch hops. Worktrees solved it without the dance: each branch in its own directory, one object store, untracked files stay put.

git worktree add -b some/branch /tmp/scratch origin/main

When you are done: git worktree remove

. If you have ever needed to fix one branch while another is mid-edit, this is the command.

Same disease as the rest of the list, incidentally. git stash

reports success and your untracked file is simply not in it.

--autosquash

prints success while leaving a fixup!

behind. A retargeted PR reports MERGEABLE

while its head is eight commits behind the branch it now claims as base. rerere.autoupdate

stages the replay cleanly and removes the unmerged path you would otherwise have been forced to look at.

In each case the message was true and the state was not what I assumed. And each one needed a different thing checked: history for the leftover fixup!

, ancestry for the stale head, the index for UU

versus M

.

Which is the actual lesson, and it is not "check the index":

A success message tells you the command completed according to its own contract. It does not tell you the repository now satisfies the condition you actually cared about. Those are different sentences. Go check the one you cared about.

Measured on git 2.39.5 locally; the GitHub compare behaviour is as of this week. Items 1–4 are reproducible on any repo in about two minutes. Item 5 is a field note from a private stack.

This post exists because Sylwia Laskowska wrote a git list good enough that I went and tested the rebase section against it, and then told me the comment should be its own post. She was right, and I would not have written it otherwise. Go read hers.

── more in #developer-tools 4 stories · sorted by recency
── more on @git 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/git-said-it-succeede…] indexed:0 read:6min 2026-08-30 ·