[BUG] stash.index=true leaves a redundant stash entry after an autostash fast-forward
- From
Eli Barzilay <eli@barzilay.org>
- Date
- Sep 7, 2026, 04:44 UTC
- Message-ID
- <CALO-guvbk2TcrVwzdNQ3yRpzHr0HHZ3h1wite0Xp0sUyAT4otA@mail.gmail.com>
Disclaimer, the following is written by an agent, but the bug is a real problem that I have.
With stash.index=true, an autostash that is applied successfully is nevertheless stored as a stash entry, and deleting MERGE_AUTOSTASH fails. A staged change at the time of the merge is required to trigger it.
Reproduction (independent of the reporter's configuration):
#!/bin/sh
set -e
export GIT_AUTHOR_NAME=A GIT_AUTHOR_EMAIL=a@b \
GIT_COMMITTER_NAME=A GIT_COMMITTER_EMAIL=a@b
rm -rf /tmp/gitbug && mkdir /tmp/gitbug && cd /tmp/gitbug
git init -q -b main up
cd up && echo a >u && echo z >z && git add . &&
git commit -qm base && cd ..
git clone -q up dn
cd up && echo more >>u && git commit -qam up2 && cd ../dn
echo staged >>z && git add z # a STAGED change is required
git fetch -q origin
git -c stash.index=true merge --ff-only --autostash origin/main
echo "--- git stash list:"; git stash listActual output:
Updating 34a5e40..84ccd9d
Created autostash: 71d4617
Fast-forward
u | 1 +
1 file changed, 1 insertion(+)
Applied autostash.
error: cannot lock ref 'MERGE_AUTOSTASH': unable to resolve
reference 'MERGE_AUTOSTASH'
--- git stash list:
stash@{0}: autostashExpected: the same without the error and with an empty stash list, as happens with stash.index=false (the only change to the script).
The autostash is applied correctly, so nothing is lost: the leftover entry duplicates what is already in the working tree and index. The command also exits 0, so the error is easy to miss, and the entries accumulate one per merge.
Reported via `git merge` above for brevity, but the common way to meet this is `git pull` with rebase.autoStash and pull.rebase set: when the pull can fast-forward, builtin/pull.c:1164 hands off to `git merge --ff-only --autostash` rather than to rebase. Every such pull with something staged leaves an entry behind.
Analysis --------
Merge keeps its autostash in the MERGE_AUTOSTASH ref (builtin/merge.c:1675) and applies it from finish() (builtin/merge.c:540). apply_save_autostash_ref() resolves the ref, applies it, and then deletes it (sequencer.c:4821-4848).
The apply is a child process, `git stash apply <oid>` (sequencer.c:4737-4751). stash.index turns that into an --index apply, which takes the index-restoring branch of do_apply_stash() and calls reset_head() (builtin/stash.c:684-691), i.e. a `git reset --quiet --refresh` child (builtin/stash.c:455-467).
That reset has no pathspec, so it calls remove_branch_state() (builtin/reset.c:543) -> remove_merge_branch_state() (branch.c:829-838), whose last statement is
save_autostash_ref(r, "MERGE_AUTOSTASH");
which stores the autostash into refs/stash and deletes the ref -- in the middle of the very apply that was about to consume it. Control returns to apply_save_autostash_ref(), the apply reports success ("Applied autostash."), and its refs_delete_ref() then fails on a ref that is already gone, producing the error line.
The child's own explanation, "Autostash exists; creating a new stash entry." (sequencer.c:4775-4779), never reaches the user because the parent mutes the apply child's output (sequencer.c:4736-4738).
A staged change is required because with a clean index the stash's base and index trees are equal, has_index is cleared (builtin/stash.c:665-668), and reset_head() is never reached.
The rebase backend is unaffected: it keeps its autostash in the file .git/rebase-merge/autostash, which remove_merge_branch_state() does not touch. Only the merge (ref-based) autostash is exposed.
Possible directions, in case they are useful: remove_merge_branch_state() is about ending a merge, and `git stash apply --index` is not ending one -- having stash's reset_head() avoid the branch-state cleanup, or teaching an in-flight autostash apply to shield MERGE_AUTOSTASH, would both close it. Making apply_save_autostash_ref() tolerate a missing ref would silence the error but leave the duplicate entry.
Versions --------
Seen with git 2.55.0 on Linux (WSL2). The code paths above are unchanged on master as of 2026-09-07.
--
((x=>x(x))(x=>x(x))) Eli Barzilay:
http://barzilay.org/ Maze is Life!