Back to Insights
21 Jun 20265 min readAI & LLMs

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-guard stops the destructive command at the door (prevention by blocking).
  • worktree-doctor shows you which worktrees hold work a cleanup would delete (prevention by visibility).
  • recover gets back committed work after the fact (cure).
  • auto-snapshot removes 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.

AI agentsrecoverygitsafety

Found this useful?