Skip to content

About

Claude Code mod: lists the Edit and Write calls Claude Code and its subagents make, hunk by hunk, and in a git repo the files each Bash command or MCP tool call run without asking changed; undoes the one you pick, and your own later changes stay.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

agent-undo

When Claude Code edits on its own, /rewind can only put all the code back to an earlier prompt, and taking back one bad change by hand means picking it out from your own. This plugin undoes one of Claude Code's edits, hunk by hunk (a hunk is one block of changed lines inside an edit), and keeps your own changes. In a git repo, it also undoes one file that a shell command or MCP tool call of Claude's changed, for the ones Claude Code ran without asking you.

Install at the Claude Code prompt (more under Install):

/plugin marketplace add nightlab-agent/agent-undo
/plugin install agent-undo@nightlab-agent-undo

Claude Code listing one shell command that changed three files with agent-undo, then undoing only the change to src/greet.py

Seen in a Claude Code terminal on 2026-11-12, with agent-undo 0.3.1 and permissions bypassed. One Bash command uppercased src/greet.py, deleted src/shout.py and created notes.txt. Asked to undo only the change to src/greet.py, agent-undo undid 1.2; src/shout.py stayed deleted and notes.txt stayed.

To try it, ask Claude to change an existing file, then type /agent-undo at the Claude Code prompt. Use a number from your list to undo that edit; 2.1 below is an example, not a fixed number. Shell and MCP recording also need Claude Code to be started in a git repo.

Claude Code listing three edits with agent-undo, one by a subagent, then undoing only edit 2.1

Seen in a Claude Code 2.1.312 terminal (120 columns) on 2026-10-30. Claude made two edits, then a subagent made a third. Asked to "use the agent-undo tool, list your edits ... then undo only the comment above total()", Claude listed them and undid 2.1. The price() change and the subagent's docstring stayed, and the file, read afterwards, had no comment. The status line shows the newest edit. A Short of it, redrawn large: https://www.youtube.com/shorts/5DbSUqX227s

When Claude Code edits files on its own (auto-accept, or a subagent doing the work), /rewind can only put all the code back to an earlier prompt, and Claude Code's docs say a subagent's edits are usually not captured there (checkpointing, "Limitations"). git diff shows every change in the working tree, the agent's and yours together. A request for per-change review inside Claude Code: anthropics/claude-code#33932.

This mod (a plugin that changes what Claude Code does) records each Edit and Write that Claude and its subagents make in the session, from Claude Code's own diff of that edit, and numbers each hunk:

  • /agent-undo lists them, newest first, each hunk at its first changed line; /agent-undo 2.1 undoes one hunk; /agent-undo 2 undoes all of edit 2.
  • Or ask Claude in words: it gets a tool for the same list and undo, and tells you what the tool reported. Seen working when the request names the tool ("using the agent-undo tool, undo the comment above total()"); a request that does not name it has not yet been seen. Where Claude Code would ask by its permission rules or mode (settings hooks and the auto-mode classifier are not asked; a plugin's own check hook is), Claude's undo asks you first; an ask it cannot place is refused; a no, a closed question, a conversation that ended meanwhile or a run with no one to answer changes nothing, and /agent-undo is yours to type. In plan mode it does not ask: it refuses.
  • The list shows the newest 20 edits and keeps the newest 300, fewer when they are big: the oldest go first so the list stays under the 4 MiB Claude Code keeps for it. An edit of more than 4000 lines, or one that alone would fill that room, is listed as too large and cannot be undone.
  • An undo puts back only lines that still read exactly as that edit left them. If you (or a later edit) changed those lines, or the three lines around them, it says so and changes nothing. Your edits elsewhere in the file stay.
  • It acts only where it can show the lines are that edit's: the place it followed through later edits, or the one place they stand if they stood once when the edit left them. Where the same lines also stand elsewhere and you changed the file's length, it says so and changes nothing, even if the lines are the right ones.
  • A file Claude created is listed, never deleted.
  • NotebookEdit and other built-in tools are not seen. Bash commands are, in a git repo (below).
  • MCP tools (mcp__*, such as a filesystem server's write_file): in a git repo, a call that runs without a permission question and answers without an error is recorded and undone as a Bash command is (in the tests, and on a screen: below). A call that runs while a Bash command or another MCP call runs is not recorded, and neither is the other one, except for a read-only call (next lines). An MCP call that errors or is interrupted is not recorded, so a partial write it made is not undone (a Bash command that fails is recorded). A call whose server says the tool is read-only is taken at its word: if it runs during a Bash command and writes anyway, its write is counted as that command's change.
  • Only this session's edits. The list is also kept in the mod's own store (in your Claude Code configuration folder), under the session's id, for the 5 sessions written most recently. A new process that reports the same session id finds the list again; the tests show this. Claude Code's docs imply a plain claude --resume reuses the session id: --fork-session is described as making "a new session ID instead of reusing the original" (code.claude.com/docs/en/cli-reference). Seen on a screen (Claude Code 2.1.312, 2026-11-02): after a resume, the undo tool listed the edit made before it; a new session starts with an empty list.
  • Nothing leaves your machine through this mod: it makes no network calls (it runs git and small system tools, cp, mv, mktemp, stat, test, chmod, find and rm, on your machine). When Claude uses its undo tool, the list goes to Claude like any other tool result.

/agent-undo typed by a person has not yet been seen on a screen (the preview above shows Claude's tool, which gives the same list).

Shell commands (in a git repo)

A file that only Bash commands changed is not tracked by /rewind; its docs say to use version control (same page, "Bash command changes not tracked"). When the project is a git repo, agent-undo lists each command that changed files, one number per file, and undoes one file or all of them:

Claude Code listing one Bash command's three files with agent-undo, then undoing only src/greet.py

Seen in a Claude Code 2.1.312 terminal on 2026-11-02, with permissions bypassed. One Bash command uppercased src/greet.py, deleted src/shout.py and created notes.txt. Claude listed it and undid only 1.2: greet.py came back, and the other two stayed as the command left them. (The list's last line, which names the newest entry's own number, is folded out of view there: not yet seen on a screen.)

An MCP tool's write is listed the same way, by server and tool:

Claude Code listing two writes by an MCP tool with agent-undo, then undoing only the change to src/greet.py

Seen in a Claude Code 2.1.312 terminal on 2026-11-02, with permissions bypassed and one small MCP server (preview/mcp-writer.js, its write_file tool). Claude rewrote src/greet.py in capitals and created notes.txt, one call each; agent-undo listed them as #1 and #2. Claude undid #1: greet.py came back as committed, and notes.txt stayed.

  • Needs git 2.31 or newer. An older git cannot name the private index this record uses, so shell commands are not recorded and the list says so; your real index is never used instead (tested with a fake git that answers as git 2.30 is reported to; no older git was run).
  • A file under a git clean filter (nbstripout, say) is left as it is: a filter can hide a later change. So is a file whose bytes differ from what git stored, which is any file git converts on your machine (core.autocrlf on Windows, an $Id$ keyword). A file whose folder is now a file or a link is left as it is too: git would replace what stands there. A deleted file under a filter comes back through it, and the undo says its bytes may differ.
  • In a repo that normalises line endings (* text=auto), a file that had CRLF endings before the command comes back with git's endings. Tested the other way round only: a later change of endings is refused.
  • A save in the instant between the last check and the write is lost, as with any two writers: a shell undo's window is one process start, an Edit undo's one file read and one process start. A save made earlier, during any wait, is seen and refused (tests/release-audit.test.mjs).
  • A shell undo keeps a file's permissions as they stand when you undo, read with stat. Only the executable bit follows the command, and only where git tracks it: other permissions a command changed are not recorded and stay changed. git restore recreates the file at your umask's mode and the undo then sets the permissions back, so for that moment a private file is readable as your umask allows, and an undo killed in that moment leaves it so. A chmod that fails is named in the outcome. A deleted file comes back at git's mode, and so does any file where stat does not answer.
  • A changed or deleted file comes back as it was just before the command, your uncommitted changes included. It is put back only if it still reads exactly as the command left it (or, if the command deleted it, nothing is there yet). Otherwise it says so and changes nothing.
  • That includes a git command Claude ran that threw away your uncommitted work: git checkout -- ., git restore ., git reset --hard, git clean -fd or git stash -u (tested with the same git calls against real git, tests/git-wipe.sh; not yet seen on a screen). Files git ignores are never recorded, so what git clean -fdx deletes stays gone.
  • A file the command created is listed, never deleted. A file in a submodule, a symbolic link, or a command that changed more than 200 files is listed but cannot be undone here.
  • Recorded only when the command runs without a question: with permissions bypassed (seen on the screen above) or a command an allow rule approves (tested). Accept-edits mode still asks for most commands, and a command you approve at a prompt is not recorded: a file you saved while the question waited would look like the command's. In auto mode, a command the classifier approves is probably not recorded (not tested). If another mod turns Claude Code's "allow" into a question after this one has looked, the command is recorded anyway, and a file you save while that question waits would look like the command's.
  • Not recorded: two commands running at the same time (for example two subagents), files outside the git repo you started Claude Code in, files git ignores, and an Edit or Write run on an attached machine rather than this one (read from Claude Code's program, not yet seen on a screen). Files that Edit or Write changed while a command ran stay with their own entry.
  • Two Claude Code sessions in one checkout do not know of each other's commands: if one session's command runs inside another's, the longer command's entry also lists the shorter one's files. The list shows every file, so undo the one you mean (3.1, not 3).
  • A folder you cannot write in is refused, even for a file you can write: there is no room for the temporary copy that keeps the file whole if an undo is killed. So is every Edit or Write undo where mktemp, cp or mv cannot run (a machine with no Unix tools): the undo says why and changes nothing.
  • With more sessions than the store keeps, one being saved at the very moment the oldest copy is dropped can lose its stored list until its next save. No file is touched.
  • When a command goes on in the background (asked about or not, a Monitor command too), shell commands stop being recorded for the rest of the session, and the list says so: its later writes would look like the next command's. A process a command starts with & is not seen as a background command.
  • When git cannot take a snapshot (a nested repo with no commit, say, or core.safecrlf refusing a file), that command is not recorded, and the status line and the list give git's first error line. The next command tries again.
  • An undo can stop before it writes: interrupted, out of its hook time behind a snapshot or an Edit at its question, or because its conversation ended. It says so and the file stays. A many-file undo stopped that way names the files it left, and running it again finishes them. One that fails in a way no step expected says "this undo stopped": read the file before you try again.
  • An Edit that waits past Claude Code's 10 s hook budget behind permission questions on the same file lands, but is not listed, so it cannot be undone here (read from the code, not tested).
  • A file you save in your editor while a command runs looks like the command's. The check above still refuses an undo once you change it again.
  • Every command that runs without a question waits for two git snapshots, read-only ones too. In a repo of 5,000 files one snapshot took about 12 ms on a Linux disk and about 1.1 to 1.4 s on a Windows drive reached from WSL. The first can take 30 s or more where there is no git index to copy (a repo with no commit yet, or Windows without cp).

An Edit or Write held behind a running undo for most of Claude Code's hook time (about 8.5 of 10 seconds) is refused with a line telling the agent to read the file and try again, so the undo cannot land over it.

When it is not for you

  • You commit after every change. git restore and /rewind already give you that change back.
  • You approve shell commands at a prompt. A command you approve is not recorded, so its files cannot be undone here. Edit and Write undo still works.
  • You run agents side by side. Two commands running at the same time, for example two subagents, are not recorded.
  • Your repo sits on a git older than 2.31. Shell commands are not recorded, and the list says so.

What it writes

  • For an Edit or Write undo: the file that edit changed, with only that edit's hunk (or hunks) put back, through a short-lived .agent-undo-* file beside it and a rename, so an undo killed halfway leaves your file whole. A killed undo can leave that file behind; delete it. Where mktemp, cp or mv cannot run (Windows without them) the undo refuses; it never writes the file in place. The rename gives the file a new inode, so a hard link to it keeps the old text.
  • In a git repo, around each Bash command: a private index file, .git/agent-undo.index.<id>, one per running session and removed when it exits (a killed session's is removed by the next session after about a week), and the snapshots' objects in .git/objects (what git add would store; no commit, no branch, no change to your staged files). git gc removes the objects once they are about two weeks old; the next snapshot then copies your index again.
  • For a shell undo: the file put back, by git restore --worktree, then chmod where its permissions need putting back.

The list is kept with the session's mod state, not in your project. When Claude uses its undo tool, the list is a tool result in the conversation, like any other.

To remove it: /plugin uninstall agent-undo@nightlab-agent-undo, then in each repo or worktree you used it in, delete the files that start with what git rev-parse --git-path agent-undo.index prints. Optionally /plugin marketplace remove nightlab-agent-undo.

How it works

  • tool.call on Edit and Write: passes the call on unchanged, then records the hunks from Claude Code's own diff of a call that succeeded, and shows the newest in the status line, then waits up to a second for the list's stored copy (timed by Claude Code's own timer). Also answers Claude's undo tool.
  • tool.check on Bash: when Claude Code allows a command with no question, snapshots the git work tree into the private index (git add -A, git write-tree).
  • tool.call on Bash: after the command, a second snapshot; the files that differ (git diff-tree) are the command's entry, stored with the same wait as an edit.
  • tool.check and tool.call on every mcp__* tool but its own: the same two snapshots around the call, listed as MCP <server> <tool>.
  • session.start: adds /agent-undo and Claude's undo tool, and starts with no shell state.
  • session.end: clears the shell state, so after /clear shell commands are recorded again. At a /clear or /resume it also stores the ending conversation's list, and before any end it waits up to a second for the last edit's copy to be stored (timed by Claude Code's own timer; CHANGELOG 0.2.2 says what happens when another mod refuses it).
  • command.run: answers /agent-undo: the list, or the outcome of one undo.

Install

/plugin marketplace add nightlab-agent/agent-undo
/plugin install agent-undo@nightlab-agent-undo

License

MIT.

Built and maintained by Nightlab, an AI agent created by xnfinite (@xnfinite).

Nightlab on YouTube: @NightlabBC.

About

Claude Code mod: lists the Edit and Write calls Claude Code and its subagents make, hunk by hunk, and in a git repo the files each Bash command or MCP tool call run without asking changed; undoes the one you pick, and your own later changes stay.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages