deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Git 2.43 autostash replayed three-day-old code over fetched fixes with no warning

A dev.to report shows pull.rebase plus rebase.autoStash on git 2.43.0 silently writing a stale working-tree edit over freshly fetched code, exiting 0 and dropping the evidence.

Git 2.43 autostash replayed three-day-old code over fetched fixes with no warning

A pull that wrote old code over new

A post on dev.to documents a git 2.43.0 failure mode triggered by two configuration lines many developers set once and forget: pull.rebase true and rebase.autoStash true. With both enabled, the author ran a plain git pull while carrying an unrelated dirty edit that was three days old. The pull fetched an upstream fix, fast-forwarded, created and applied an autostash, and exited with status 0. Nothing in the output read as a warning.

The resulting file mixed eras. One line carried the upstream fix that had just arrived; another carried the three-day-old local edit, replayed on top of the update. Checking the file at HEAD confirmed the stale line existed only in the working tree, not in any commit. The modification time was stamped at the current second, so the file's age and its provenance pointed in opposite directions.

The mechanism is ordinary merging

According to the post, the behavior follows directly from how autostash works. Git stashes the dirty tree, moves HEAD to the upstream commit, then re-applies the stash onto the freshly fetched files. A stash is a diff, and a diff has no memory of when it was made. When the stale hunks and the upstream hunks sit in different regions of a file, the three-way merge succeeds, git prints "Applied autostash.", and the result is a hybrid file with no marker saying which parts are current and which are old. The post includes a self-contained shell reproducer that demonstrates the effect in about a second.

The conflicting variant also exits zero

Changing the reproducer so the stale edit and the upstream fix touch the same line makes the autostash application conflict. Git announces the conflict on stdout and says the changes remain safe in the stash — then, per the post, returns exit code 0 anyway, leaving conflict markers and an unmerged path in the working tree. The author's point is that any deploy script running git pull && make, or any CI step checking $?, will pass straight through, because the one channel scripts actually read is the one reporting success.

The stash vanishes in the silent case

In the conflict case the stash survives. In the clean, silent case — the one the author considers the dangerous one — git drops it. The post shows an empty stash list, no stash reflog, and only a dangling commit discoverable through git fsck --no-reflogs, an object garbage collection will eventually remove. Anyone investigating a week later how an old line reappeared would find almost nothing.

Why the author's own gates stayed green

The author runs a small automation tool that commits work produced by software agents, gated on three criteria printed in every commit message: a file counts as settled once its modification time has been stable for over ten minutes, it must be parse-clean, and it must be reviewed. A replayed stale tree satisfies all three by construction. The pull stamps a current modification time, so a three-day-old line reads as settled ten minutes later. Old code was well-formed when written, so parse checks and even test suites pass, since the reverted version was green when it shipped. The commit message template, meanwhile, is identical for genuine fixes and for rollbacks.

The consequence, according to the post: a revert of a retry loop landed under the wording of a settled fix, then permanently conflicted with upstream and jammed the lane that had landed it. The author counted at least 38 related posts across a three-day window, and notes that figure is a floor because the window slides.

Detecting rollbacks by content identity

The question none of the gates asked, the post argues, is where the bytes came from. The proposed check is cheap: hash the candidate file with git hash-object and compare it against the blob stored at that path in every ancestor of HEAD. A byte-identical match means the tree moved backwards rather than forwards. Run against a working tree holding an old version, the script flags the file as identical to a named historical commit.

Why it matters

pull.rebase and rebase.autoStash are widely recommended for keeping pulls tidy when the working tree is dirty. This account shows the combination can silently reintroduce reverted code — with a success exit code, a brand-new timestamp, self-erasing evidence, and commit messages indistinguishable from real work. Pipelines that trust exit codes, file ages, or well-formedness checks after a scripted pull are exposed. Provenance checks that compare file content against historical blobs — or simply avoiding autostash in automated pulls — close a gap the usual gates cannot see.

  • #git
  • #version-control
  • #developer-tools
  • #data-loss
  • #ci

Related posts