The One Kind of Agent Work You Can't Recover — and How to Stop Losing It
Every recovery post we've written ends on the same line: commit early. There's a reason it keeps coming up. Committed work survives almost anything — a removed worktree, a reset --hard, a deleted branch — because the commit object sits in git's store and fsck can find it. Uncommitted work has no such net. A file an agent edited but never git add-ed is just a file; when the worktree gets cleaned, nothing was ever written to the object store, and there is nothing to recover. That's the loss in claude-code#46444 — ~300 never-committed files, gone.
"Just commit early" is correct and also useless as advice, because the whole point of an agent is that you're not sitting there typing git commit. So make it automatic. Claude Code fires a Stop hook every time the agent finishes a turn — the perfect moment to snapshot whatever's in the working tree, so the never-committed state simply stops existing.
The catch is doing it without getting in the agent's way. You can't git commit on the agent's branch every turn (you'd bury its real history in WIP noise) or git stash (that reverts the working tree out from under it). The snapshot has to be completely invisible: no change to the working tree, the index, the branch, or the visible history. Here's how.
The trick: snapshot into the object store, touch nothing else
A git commit is just a tree plus parents. You can build one from the current working tree without disturbing anything the agent is doing, by staging into a throwaway index:
export GIT_INDEX_FILE=$(mktemp) # a scratch index — NOT .git/index
git read-tree HEAD # seed it from the last commit
git add -A # stage everything dirty, including untracked files
tree=$(git write-tree) # a tree object of the exact working-tree state
sha=$(git commit-tree "$tree" -p HEAD -m "auto-snapshot")
unset GIT_INDEX_FILE # the real index was never touched
GIT_INDEX_FILE is the load-bearing line: every git add / write-tree writes to the scratch index, so the agent's real index and working tree are untouched. The result is a commit object hanging off HEAD that captures the complete dirty state — and crucially including untracked files.
Why not git stash create?
This was the bug in our first version, and it's worth flagging because it's the obvious approach. git stash create makes a stash commit without touching your tree — perfect, except it silently ignores untracked files. The single most important case — a brand-new file the agent just wrote and never staged — is exactly the case git stash create drops. The throwaway-index method captures it because git add -A sees untracked files (while still respecting .gitignore, so node_modules stays out).
Pin it so gc can't drop it
A dangling commit is recoverable for ~2 weeks, until git gc prunes it — and a machine churning agent worktrees triggers gc often. So pin each snapshot under a ref:
git update-ref "refs/wip-snapshots/$(date +%s)" "$sha"
Now the snapshot is reachable, survives git gc --prune=now, and is trivially listable. We verified the gc-survival in the test suite — a snapshot taken, then git gc --prune=now, then still resolvable.
The tool
auto-snapshot wraps that into a Stop hook plus a tiny CLI:
auto-snapshot # the hook action — snapshot now (no-op if clean)
auto-snapshot list # newest first: age · sha · branch · diffstat
auto-snapshot restore <sha> # bring a snapshot's files back into the working tree
auto-snapshot prune --keep 50
Wire it into .claude/settings.json and forget it:
{ "hooks": { "Stop": [ { "hooks": [
{ "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/auto-snapshot" }
] } ] } }
From then on, every time the agent stops, the dirty tree is snapshotted into refs/wip-snapshots/…. When something goes wrong — the worktree got cleaned, the agent ran a destructive command, you reset-ed too far — auto-snapshot list shows you the timeline and auto-snapshot restore latest puts the work back. It's the difference between "the agent's last four hours are gone" and "restore latest."
How it fits
This is the automated version of the lesson the rest of our agent-ops tools keep pointing at:
agent-guardstops the destructive command at the door (prevention by blocking).worktree-doctorshows you which worktrees hold work a cleanup would delete (prevention by visibility).recovergets back committed work after the fact (cure).auto-snapshotremoves the one gap the others can't cover — the never-committed state — by making sure there's always something in the object store to recover (prevention by snapshotting).
Guard blocks, doctor warns, recover cures — and auto-snapshot makes the cure always possible.
The honest caveats
Snapshots are commits in your object store, so a very noisy repo accrues a lot of them — hence prune (default: keep the newest 50); run it from the same Stop hook if you like. restore overwrites a path with the snapshot's version (it's a recovery action, point it at a tree you actually want back). It respects .gitignore by design, so deliberately-ignored files aren't captured. And it's per-repo: it protects the working trees git knows about, which — paired with worktree-doctor across the fleet — is the coverage you want.
The real lesson
You don't prevent uncommitted-work loss by remembering to commit. You prevent it by arranging that uncommitted work is never the only copy. Git already has everything needed — a scratch index, commit-tree, a ref to pin the result — to take a perfect, invisible snapshot on every agent turn. Forty lines of that, wired to a Stop hook, turns "commit early" from a discipline you'll forget into a property of the system.
We build and run autonomous agent fleets on Claude Code in production. If you're hardening a multi-agent setup, talk to us.
Get the tool → auto-snapshot 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 Agent Did the Work, Then It Vanished. Here's How to Get It Back.
- Your Claude Code Session Is Permanently Stuck on a 400. Don't /clear — Do This.
- 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…