Two sessions, one working tree
A second session discarded the first one's uncommitted work. Why none of it came back, and three precautions that would have helped.
Published on Category: Field Notes
On this page
If an agent edits files in place without ever running git add, and something then resets the tree,
there's nothing to recover. No stash, no commit, no dangling blob. That's not a git failure, it's the
object model working as designed, and it's the part people get wrong when they say git never loses
anything. It's happened to me twice, both times documented, and the three precautions below come
straight out of those two cases.
Check this first if you're here after the fact
Two commands tell you whether recovery is even possible:
git reflog
git fsck --lost-found
git stores content in three places, and only two of them are lifelines.
The object database. git add writes the file content as a blob into .git/objects. From that
moment it exists, commit or no commit. A hard reset turns it into a dangling blob, and
git fsck --lost-found finds it again.
The reflog. Every commit, every branch switch, every reset leaves an entry. This is the case people have in mind when they say git never loses anything.
The working tree. A changed file that's neither staged nor committed is just a file on disk to git. No object, no hash, no entry.
If your work was only ever in the working tree, both of these will look completely normal and neither
will contain it. git reflog shows every position HEAD has held, not what sat on disk beside it.
git fsck --lost-found shows objects that no longer hang on a ref, and an unstaged file never was
one. What's left is editor backups, your operating system's file versions, and terminal scrollback.
If you're not here after the fact, the rest of this is how the tree got lost in the first place, and what stops it.
What happened
On 2026-05-28 a session ran four waves in one repo. An error-handling migration, 582 throw sites converted, roughly 480 previously skipped tests re-enabled, typecheck at zero errors. None of it was committed, and that was by design: the agents edit files directly and unstaged, the commit comes at the end.
A second session was running in parallel. Not a second clone, not a second working tree, the same
directory, and according to the note even the same semantic session id. It ran git reset, discarded
the entire working tree of roughly 103 changed files, and then started its own work on other routes.
Four waves of work were gone. How much of it got rebuilt and how long that took isn't recorded anywhere.
The learning note from that day puts it like this (confidence 0.85, status verified):
"The work was UNRECOVERABLE: agents edit in-place unstaged (per orchestrator instruction), so reset/restore leaves no stash, no commit, no dangling blobs."
The note lists its source session as unknown and the session file is gone. Why the second session
ran the reset isn't recorded anywhere. I don't have more detail than that.
The second incident, where nothing happened
Three weeks later, different repo, 2026-06-21. A review agent that's read-only by contract needed a
diff against a baseline. It ran git stash, switched to the base ref with git checkout, compared,
then restored everything. Afterwards the tree was byte-intact, HEAD was fine, the stash was empty,
572 tests green.
The note still files it as an anti-pattern:
"Working tree verified byte-intact, but this is a PSA-003 violation that risks data loss."
So nothing happened. That's an outcome, not a property. It came out that way because no other agent touched the same files in that window.
A third case, 2026-08-06, shows how narrow that window is. A wave agent wanted to prove that a
formatter warning predated its own change, and reached for git stash to do it. That put 58 entries
into the stash, 24 of them untracked, meaning the in-flight work of every parallel agent, in a place
none of them would have looked. About 40 seconds passed before the pop. In that time three other
agents kept writing, and git status counted 63 entries afterwards instead of 60. The pop went
through without conflict because those three happened to touch different files.
The original question never needed the working tree at all:
git show HEAD:path/to/file.ts | pnpm exec prettier --stdin-filepath path/to/file.ts --check
Twice nothing happened, and both times it came down to which files the other agents happened to be touching. That isn't a safety model, it's luck with a log.
How often this comes up
I keep a learning note per session in one directory. Here's the measurement:
cd ~/Projects/vault/40-learnings
find . -name '*.md' | wc -l
grep -rilE 'git reset|git stash|uncommitted working tree|unrecoverable' --include='*.md' . | wc -l
41 out of 4421 notes as of today. 22 of them name git stash specifically. Spread across more than
a dozen repos and across months. Those are mentions, not confirmed incidents. 41 is the ceiling, not
the count.
The uncomfortable part: a prohibition in the prompt isn't enough. A session from 2026-07-16 records three incidents, all of them after an explicit ban, all of them disclosed and reverted by the agent itself. What changed afterwards wasn't the wording of the ban, it was the list of commands the agent can no longer run at all.
One more number, from a note dated 2026-06-02, explains why this isn't an edge case: the host had 24 Claude processes running at the time, and the session lock got re-taken by a peer mid-session.
Three precautions
Checkpoint commit after every implementation wave
The mitigation is spelled out in the note on the first incident: when the working tree is shared, commit to a session branch after each implementation wave instead of once at the end. It doesn't prevent the reset. It caps the damage at the wave you're in.
Two details hang off this, and both have their own incidents.
In a shared tree, git add -A and git commit -a will sweep the other session's work into your
commit. That's exactly how a peer session's refactor ended up in someone else's commit on 2026-06-02,
healed by amending, nothing lost. And a bare
git commit commits everything that's staged, including what another agent staged between your add
and your commit. That note carries confidence 1.0 and two incidents inside a single session. So:
pathspecs, always.
git switch -c session/2026-08-14-migration
git commit -m "wip(w2): checkpoint" -- src/lib/a.ts src/lib/b.ts
Files git isn't tracking yet don't get included that way. Stage those one at a time with
git add path/to/file, never with -A.
Session lock at start
Also from the first note, buried in an aside: capture the value from the session lock at start, and if it changes mid-session, a sibling has taken over. Stop and verify the tree before doing anything else.
# once at session start, remember the value it prints
uuidgen | tee .git/session.lock
# before every wave
cat .git/session.lock
Which value it is doesn't matter, it just has to differ between two sessions. $$ won't do: every
bash line runs in a new shell and gets a new PID.
This prevents nothing. It shortens the time until you notice, and combined with checkpoint commits that's the difference between losing one wave and losing four.
Read-only means diff, not stash
A review or test agent that needs a baseline already has one. HEAD, every ref and every commit are readable without touching the working tree:
git diff HEAD
git diff <base-ref>..HEAD
git show HEAD:path/to/file.ts
If an agent does stash anyway, the pop belongs in the same step, not in some cleanup afterwards. In one case from 2026-05-08 a test agent had stashed a catalog file and never restored it. It only surfaced when the coordinator cleaned up, and after the pop the file's 204 entries were complete again.
The fix that actually removes the problem
The three precautions above limit damage.
git worktree add removes the cause: every session gets its
own directory while sharing one object database, so a reset in one can't reach the other.
I'm not pretending that's free. Separate trees mean separate installs and separate build caches, and for short sessions that overhead is real. But if you run parallel sessions regularly, this is the one measure on this page that changes the shape of the problem instead of the size of the loss.
The trap in the safety net
The obvious reflex is an automatic snapshot before each dispatch. I had one, and it backed up the wrong thing.
The snapshot went through git stash create, because that doesn't touch the working tree. Untracked
files weren't in it. For waves that produce a lot of new files, those snapshots were effectively
empty backups: the command exited cleanly, the backup existed, and it would have been worthless when
needed. It only surfaced when somebody listed the trees of the stored snapshots directly.
The obvious fix, appending -u, didn't fix it. Measured on git 2.53, git stash create accepts the
flag and ignores it; only push and save honor it
(the docs list -u under push and save, not under create,
retrieved 2026-08-14). That note is still a draft at confidence 0.6, so check it against your own
version.
What's left of it: a backup is only a backup once you've read its contents. Exit code 0 isn't a backup.
What the three measures don't cover
What's above is three individual measures from two incidents. They cover exactly the incidents they came from. What they aren't is a procedure that turns every new incident into the next rule, keeps those rules provable, and drops the ones that stopped catching anything. That's what module 3 of the multi-agent orchestration course is about, for when you run parallel waves regularly rather than surviving them once.
What I don't know
The session file for the first incident is missing. Whether the second session decided to reset on its own or was told to, I don't know, and I won't find out.
And a counterexample, so nobody gives up too early: in June 2026 a commit was declared lost after a
thorough search. Not on GitLab, not on GitHub, in no local clone, not in the trash, not in the file
index. A git pull then brought it in, because another machine had pushed the same work under a
different SHA. 28 files, complete. "Not reachable here and now" isn't the same as "gone". For an
unstaged working tree that doesn't help, for a missing commit it very much does.
Questions I get
Is git stash bad in general?
No. In a tree where you're the only one working, it's the right tool. The problem isn't the command, it's its reach: it operates on the whole tree, including the parts that belong to someone else.
How much access should an agent get in the first place?
That's the other half of the same question, and six stages before write access answers it in more depth than this piece does. The short version: write access to the working tree is one stage, and the commands that move the tree as a whole are a considerably higher one. Where the agent runs while doing that is its own question, covered in running a local model with Claude Code.