Your Agent Did the Work, Then It Vanished. Here's How to Get It Back.
There is a specific kind of dread that only exists in 2026. You set an agent loose in a worktree, go make coffee, come back — and the work is gone. Not "needs review" gone. Gone gone. The branch isn't there. git log doesn't show it. The worktree directory has been swept up. Hours of an agent's output, evaporated.
We've run head-first into this enough times to have learned the one fact that decides whether you get the work back or not. It is not the fact the panic-Googling will tell you first. So here it is up front, because it's the whole post:
If the agent committed, your work is almost certainly still there. If it didn't, it's almost certainly not. Everything else is detail.
This is a real, named failure mode
We're not extrapolating from one bad afternoon. The Anthropic claude-code tracker has it documented in several forms:
- #34327: Claude Code ran
git reset --hard origin/mainautonomously within the first second of session startup — twice — "destroying 12 unpushed commits + all uncommitted work." Crucially, the reporter recovered the committed half: "partially recovered via reflog." The uncommitted half was gone. - #46444: worktree auto-cleanup "silently cleaned up the previous session's worktrees without committing… ~10 days of development… permanently deleted." ~300 files. Never committed — never recovered.
- #55724: with 13 parallel agents, "5 committed successfully, 8 failed due to lock contention" — and the worktrees of the 8 that failed were auto-removed before they could commit.
It isn't an Anthropic thing, either. Cursor's forum has the same shape: an agent runs git restore or git stash mid-session and uncommitted edits disappear. Different vendor, identical boundary.
Notice the pattern across every one: the committed work that got recovered came back through git's own safety nets. The work that was never committed is the work that stayed lost.
Why committed work survives — and where it actually lives
When an agent runs git commit, git writes a commit object into the shared object store under .git/objects. That object does not get deleted when the branch is deleted, when the worktree is removed, when HEAD is reset, or when the agent crashes. It just becomes unreachable — an object no ref points to anymore. Unreachable is not the same as deleted.
There are two safety nets that find unreachable commits, and the difference between them is the part most people get wrong:
The reflog is the everyday net. git reset --hard, a botched rebase, a branch that moved — the old tip is still in git reflog, and you just git reset --hard HEAD@{1} your way back. We verified the textbook case on Git 2.50.1:
$ git reset --hard HEAD~2 # two commits "gone"
$ git reflog
d7f4c1d HEAD@{0}: reset: moving to HEAD~2
c5c5632 HEAD@{1}: commit: feat: agent work part 2
6c211c4 HEAD@{2}: commit: feat: agent work part 1
$ git branch rescued c5c5632 # back
But the reflog will not save you from a removed worktree — and this is the trap. A worktree's HEAD reflog lives inside that worktree's metadata (.git/worktrees/<name>/logs/HEAD). When the worktree is removed, that reflog is deleted with it. We confirmed it: after git worktree remove --force, the per-worktree reflog is gone, and the commit survives only as an unreachable loose object in the shared store. git reflog shows you nothing.
For that case you need the deeper net: git fsck.
$ git worktree remove --force ../wt-feature
$ git branch -D feature # nothing points at the commit now
$ git log --oneline --all
583774d initial commit on main # the agent's commit is gone from history
$ git fsck --no-reflogs --dangling
dangling commit 3514f038bc7d5b0157ca9cf65a8ee4223c29586f
$ git show 3514f03 # there's your work, intact
$ git branch recovered 3514f03 # back
--no-reflogs is the trick that makes the output usable: it lists only commits unreachable from refs and reflogs — the true orphans — instead of drowning you in everything.
The honest boundary, and the clock
Two caveats, both verified for real, both load-bearing.
Uncommitted, never-staged work is genuinely gone. Files an agent edited but never git add-ed are ordinary files; if the worktree directory was deleted, nothing was ever written to the object store, and fsck has nothing to find. We tested it — recover list correctly reports nothing. (One subtlety: git add alone does write a blob, so staged-but-uncommitted content resurfaces as a dangling blob — raw content, no filename. Only the never-staged stuff is truly unrecoverable.)
Recovery is time-bounded. Unreachable objects don't live forever. On a fresh repo the defaults are gc.pruneExpire = 2 weeks (loose unreachable objects) and gc.reflogExpireUnreachable = 30 days. Worse, gc.auto (~6700 loose objects) means a routine git command can trigger a gc that prunes early — and a machine churning many short-lived agent worktrees fills that threshold fast. We proved the cliff:
$ git gc --prune=now
$ git show 15f10196
fatal: bad object 15f10196... # permanently gone
So the operational rule when you've just lost something: stop. Don't run more git commands than you need to, and do not run git gc or git worktree prune first. Recover, then clean up.
The 70-line rescue tool
We wrapped the whole playbook into one script, recover, test-first (7/7 green: worktree-remove → recoverable, reset → recoverable, after gc --prune=now → correctly gone, never-staged → correctly nothing). The demo above walks through losing a commit to a removed worktree and getting it back:
recover list # dangling commits, newest first: sha · author · date · subject
recover show <sha> # the full diff, to confirm it's the one
recover to <branch> <sha> # restore it onto a fresh branch
recover list runs git fsck --no-reflogs --dangling, sorts newest-first, and prints a subject + diffstat for each — because fsck is noisy (every --amend, rebase, and dropped stash leaves dangling commits too), and the subject line is how you spot your commit in the pile. One bash gotcha we hit and fixed, worth passing on: piping a set -e print-loop into grep -q truncates the pipe and trips a SIGPIPE failure — so the tool builds its whole report in a buffer and emits it with a single printf.
The real lesson
The tool is the small part. The lesson is the boundary: the single most valuable thing you can do for an agent's work is make it commit, early and often — even throwaway WIP commits. A commit is the line between "recoverable for two weeks" and "gone forever." Everything recover, reflog, and fsck do only works on the right side of that line.
We build and run autonomous agent fleets in production. If you're hardening a multi-agent setup, talk to us.
Get the tool → recover in agent-ops — open source (MIT), a single dependency-free script, part of our toolkit for running coding agents in production.
Found this useful?
- You're Running Five Agents on One Repo. Who Stops Them Overwriting Each Other?
- Your Claude Code Session Is Permanently Stuck on a 400. Don't /clear — Do This.
- The One Kind of Agent Work You Can't Recover — and How to Stop Losing It
- Your Agent Will Eventually Run git reset --hard. Stop It at the Door.
- Your Agent Reads Text You Can't See. Frisk It First.
- You're Running a Dozen Agents. Which One Is Stuck?
- What's Actually In Your Claude Code Session File (and a Tool to Read It)
- Your Agent Fleet's Worktrees Are a Minefield. Here's How to See It.
- The Agent Wrote the Code in Minutes. Reviewing It Takes Hours. Here's Why.
- Your Claude Agent Re-Reads Its Whole Prompt Every Turn. Here's the 10× You're Leaving On the Table.
- Your Agent Can Read Everything Your Shell Can. Have You Looked?
- The Loosest Line in Your Repo Is in .claude/settings.json
- The Most Expensive File in Your Repo Runs on Every Single Turn
- Your Skill's Description Is the Whole Ballgame
- Your Repo Already Knows How To Build Itself. Put That In the CLAUDE.md.
Related posts
The boring part: governance that enforces, not displays
Most "AI governance" is a dashboard. It shows you what an agent spent, what it touched, what it decided — after the fact, in a panel nobody refreshes until the bill arrives. A display is not a control. A control is the t…
Your Launch Deserves a Real Demo Video, Not a Screen Recording With Captions
Most product launches skip the demo video entirely, and the ones that don't ship one usually reach for the fastest option: a raw screen recording, maybe some auto-generated captions, posted as-is. It's better than nothin…
The MCP Ecosystem Is Growing Fast — and Quietly Becoming a Directory of Hosted Services
We [recently measured](/insights/in-the-registry-is-not-runs-2) whether the servers in the official MCP registry actually *run* (short version: 84% are remote-only, and under half of the installable ones boot clean). Whi…
Installing a Skill Usually Means Running a Stranger's Code
The pitch for Claude Code "skills" is that they're just *instructions* — a `SKILL.md` file with some markdown telling the agent how to do a task. That framing makes them feel safe to install. A prompt can't hurt you; wor…