Undo (src/undo/, src/session/undo.ts)
This page is generated from
docs/lld/undo.mdin the repository.
Purpose
Section titled “Purpose”/undo takes back the last turn: the files go back to how they were before the turn, and the turn leaves
the conversation, so the model forgets it. /redo brings both back. Added in 0.4.
It covers the files in its snapshot, not only the changes of edit_file and write_file: a command
in the sandbox can write files with no approval, and undo takes those changes back too. Ignored files
(build output, node_modules) and the paths listed below are not in a snapshot, so undo does not
restore them; with undo off, or after a failed snapshot, there is nothing to restore. It works in any folder, with or
without git, and it never touches the project’s own .git.
| File | Role |
|---|---|
src/undo/snapshots.ts |
SnapshotStore: take a snapshot, list the changes between two, restore one. |
src/undo/question.ts |
The question before /undo and /redo: the turn, the files, the conversation. |
src/session/undo.ts |
The conversation side: undo points and the redo stack, as pure steps. |
Snapshots
Section titled “Snapshots”A snapshot store is a git folder of Garuda’s own: ~/.garuda/snapshots/<first 16 hex of sha256(root)>
(mode 0700). The project is its work tree; the store has its own index.
| Step | git |
|---|---|
| Take | ls-files --cached --others --exclude-standard (count), add -A, write-tree → a tree id |
| Changes | diff-tree -r -z --no-renames --name-status <from> <to> |
| Restore | read-tree -m -u <from> <to>: new, changed and deleted files; it refuses when a file changed after <from> was taken |
.gitignorerules apply, so ignored folders (node_modules, build output) are not in snapshots, and undo does not change them.- Never in a snapshot:
.git,.garuda/sessions/,.garuda/index/,.garuda/evals/..garuda/memory.mdand the settings are in it. - git runs through the Executor (N8), outside the sandbox (the store is outside the project), with no
system or global config (
GIT_CONFIG_NOSYSTEM,GIT_CONFIG_GLOBAL=/dev/null), no hooks and no fsmonitor. Tree ids are checked before they go into a command. - Sandbox commands cannot write
~/.garuda, so they cannot change the undo history. git gc --autoruns every 20 snapshots: it packs the objects, and git prunes snapshots that no longer matter after two weeks (its default). An undo point older than that fails with a clear message.
Runtime.runTurn takes a snapshot before the user message and records a snapshot record: the tree, the
message count before the turn, and the prompt’s first line. Plan-mode turns get one too.
| Case | Result |
|---|---|
| The snapshot fails (no git, too many files, an error) | Undo is off for this session; one notice; the turn runs. |
| A snapshot takes more than 10 s | Undo is off for this session; one notice (add big folders to .gitignore). |
| More than 50 000 files | No snapshot (the first git add would be too slow). |
/undo and /redo
Section titled “/undo and /redo”- Take a snapshot of the files now.
- List the changes from now to the target (the last undo point, or for
/redothe state at the undo). - Ask, with a yes/no question: the turn, the files (
+comes back,-is removed,~changes, at most 30 lines), and what happens to the conversation. Changes that the user made after the turn go back too, so it always asks. - Restore the files (
read-tree, which refuses when a file changed during the question), then change the conversation and write anundoorredorecord.
Conversation rules (src/session/undo.ts):
- An undo point holds the message count before its turn.
/undocuts the messages there; the cut part goes to the redo stack./redoputs it back. - A new user message ends the redo chain.
- After a compaction, the earlier turns are no longer separate messages: undo then restores only the
files, and the next turn gets a
<garuda_note>that the user undid the turn. - At most 50 undo points per session.
- The records are in the session file, so
/undoworks aftergaruda --resume; replay applies them too.
The question says that files which .gitignore covers (build output) are not in undo (0.14.1, review):
“No file changes” read as if the turn had changed nothing.
/diff (0.6)
Section titled “/diff (0.6)”Runtime.diff(scope, path) compares a base snapshot with a new one taken now (so the user’s own changes
count too):
session: the firstsnapshotrecord in the session file (cached per session id). It survives the 50-point limit and/undo.last: the last undo point, the files before the last turn that is still in effect.SnapshotStore.stats(from, to):changes()plusdiff-tree --numstatcounts (none for binary files).SnapshotStore.patch(from, to, path?):diff-tree -pwith no color, no external diff and no textconv. The path is checked withresolveInRoot, quoted as one shell word, and given as a:(literal)pathspec.- Undo off, no turn yet, or a path outside the root: a message, no error. Git errors (for example, a snapshot that git pruned after two weeks) become a message too.
Turn it on or off
Section titled “Turn it on or off”On by default in the CLI (chat and -p, so a later chat can undo a -p task). "undo": { "enabled": false }
in .garuda/settings.json turns it off. The Runtime option undo enables it; tests and evals leave it out.
Cost (0.4, measured in the cloud workspace)
Section titled “Cost (0.4, measured in the cloud workspace)”| Project | First snapshot | Next snapshot, no change | One changed file | Store |
|---|---|---|---|---|
| Garuda repository (~230 files) | 137 ms | 15 ms | 17 ms | 1.7 MB |
| 20 000 small files | 1.3 s | 66 ms | 88 ms | 84 MB (before packing) |
test/notifyDiff.test.ts (0.6): /diff for the session, the last turn, one path, the user’s own change,
the cut and Ctrl-O, undo off. test/undo.test.ts: take, changes and restore (added, changed, deleted files); .gitignore, Garuda’s
records and the project’s own git untouched; a file that changed after the snapshot; too many files and bad
ids; the store folder and its mode; the conversation steps and their records (undo, redo, a new prompt, a
compaction); /undo and /redo through Runtime with an edit and a command; “No”; undo after
--resume; a missing snapshot; the chat commands; off by default in Runtime and with the setting; a
failing snapshot; replay with an undo; the question text.