DEV Community

Krishi Attri
Krishi Attri

Posted on • Originally published at github.com

stepback: rewind AI coding agent edits without touching your real git

an AI coding agent edits six files, decides one needs a full rewrite, and drops a stray debug_output.json in your repo root while it's at it. now you want the last good state back. git stash doesn't help, the agent never committed anything, there's nothing to stash. your editor's undo stack unwinds one file at a time, if it even covers all six. so you do it by hand: git diff, squint, revert some hunks, delete the junk file, hope you didn't miss anything.

i built stepback for that moment. it's a CLI that wraps your agent, stepback run -- claude, stepback run -- codex, stepback run -- aider, or anything else that edits files on disk. it snapshots your working tree as the agent works and lets you rewind to any snapshot exactly, deleted files and binaries included.

$ stepback run -- claude
  ... let the agent work ...
$ stepback list
  #7    2m ago  3 file(s) [~3]      +conv:claude-code
  #6    5m ago  1 file(s) [+1]      +conv:claude-code
$ stepback diff 6
$ stepback rewind 6
  restored to checkpoint #6.  (`stepback redo` to undo this rewind)
Enter fullscreen mode Exit fullscreen mode

how it snapshots without touching your git

the constraint that shaped the whole design: stepback must never be able to corrupt or even nudge the repo you're actually working in. no writes to HEAD, no commits on your branch, no touching your staged changes. so it doesn't use your real index at all.

every checkpoint runs through GIT_INDEX_FILE pointed at a throwaway index, not .git/index:

GIT_INDEX_FILE=<tmp>  git add -A .        # into a throwaway index, respects .gitignore
GIT_INDEX_FILE=<tmp>  git write-tree      # -> tree SHA, content-addressed
Enter fullscreen mode Exit fullscreen mode

that tree gets committed with commit-tree under stepback's own author identity (so it works even if you have no git user configured locally) and pointed to by a ref under refs/checkpoints/<session>/<n>. never a branch ref, never HEAD. identical trees dedupe automatically, so an idle agent doesn't spam checkpoints. outside a git repo entirely, stepback creates a private bare object store at .stepback/shadow.git and runs the same plumbing against that instead.

restore is the mirror image. diff the current tree against the target, stage only the files that actually changed into a temp location with checkout-index, delete what shouldn't exist anymore, then move each staged file over the real one with an atomic same-filesystem rename. if a restore dies halfway, every individual file is either fully old or fully new, never a half-write. and before any of that runs, stepback commits your current state and pushes it onto a redo stack protected by its own ref, so stepback redo works even if the rewind itself gets interrupted.

the isolation guarantee, and how it's checked

"never touches your real git" is easy to claim and easy to accidentally violate the first time someone rewinds mid-merge. so it's tested directly, not just asserted: the suite checks that the index, HEAD, and branch are byte-for-byte unchanged after a checkpoint-and-rewind cycle under a normal repo, a detached HEAD, a repo with MERGE_HEAD present, and a repo with staged-but-uncommitted changes sitting in the index. 67 tests total, covering that isolation guarantee plus exact restore of binaries, symlinks, unicode and space and leading-dash filenames, .gitignore handling, crash recovery, and cross-process locking, so a running watcher and a manual rewind typed in another terminal can't corrupt each other's state.

two layers, and i mean it about "best-effort"

file checkpoint and restore is layer 1, and it's the part i'd stake a claim on. solid, tested, the part that's actually hard to get right: git plumbing, atomicity, crash recovery. layer 2 sits on top of that. stepback also snapshots the agent's on-disk session transcript at each checkpoint (~/.claude/projects/<slug>/<uuid>.jsonl for claude code, the newest file under ~/.codex/sessions/ for codex), so a rewind can hand you a resume hint like claude --resume 9f3c... and you pick the conversation back up from that point, not just the code.

i'm not going to oversell that part. those are private, undocumented session formats with zero stability guarantee, owned by vendors who can change them in any release. layer 2 is quarantined behind an adapter interface on purpose: every adapter method is wrapped in try/except at the engine level, so an adapter that raises on every single call still can't touch layer 1. if your agent isn't claude code or codex, or the format shifted under an update, stepback drops to file-only rewind silently. no crash, no half-broken state, just one fewer convenience. that's the whole policy: a real result or a clean degrade, nothing in between.

try it

pip install stepback
stepback run -- claude          # or -- codex, -- aider, or any command
stepback status                  # storage mode, session, watcher, detected adapters
stepback list                    # checkpoints, newest first
stepback diff <id>               # what a checkpoint changed (-w to diff vs current tree)
stepback rewind <id>             # preview + confirm + restore
stepback rewind <id> -n          # dry run, show the plan, touch nothing
stepback redo                    # undo the last rewind
Enter fullscreen mode Exit fullscreen mode

it's 0.1.0. the file layer is the part that's had the real engineering effort and it's where i want scrutiny, issues and PRs welcome, especially edge cases in restore. code is at github.com/Archerkattri/stepback, package is on PyPI.

Top comments (1)

Collapse
 
topstar_ai profile image
Luis Cruz

I appreciate how stepback utilizes a throwaway index to snapshot the working tree without interfering with the actual Git repository. The use of GIT_INDEX_FILE to point to a temporary index is particularly clever, as it ensures that stepback never corrupts or modifies the user's Git repository. I've worked on similar projects where maintaining a clean and separate state is crucial, and I can see how this approach would be beneficial in preventing unintended changes. The testing suite's focus on verifying the isolation guarantee, including scenarios like mid-merge rewinds, also gives me confidence in stepback's reliability. How do you envision stepback handling more complex Git workflows, such as rebasing or cherry-picking, and are there plans to expand its support for these scenarios?