Salvager — Your agent broke it. Get it back.<br>Skip to content
Your agent broke it.<br>Get it back.
Salvager is a filesystem-level safety net for AI coding agents. A passive<br>watcher saves every file revision as you and your agent work, so anything<br>the agent breaks, you can get back.
View on GitHub
How it works
Zero config. Run it in any project root:
shell Copy<br>salvager watch
The problem
An agent can rewrite a file faster than you can read the diff.
AI coding agents edit real files on disk. Most of the time that's<br>exactly what you want. But mistakes are silent. A clobbered function, a<br>deleted block, a refactor that ran too far, and by the time you notice,<br>the previous version may already be gone.
Recovery shouldn't depend on the agent being careful, on you having<br>committed at the right moment, or on an editor that happened to be open.<br>You need a guarantee that lives below the agent, down at the filesystem.
How it works
A passive watcher, not a workflow you have to remember.
Think Local History for AI agents: capture is automatic, recovery is<br>whatever you want, whether CLI, MCP, or your bare hands. The two are<br>decoupled on purpose, so the part that protects you never depends on the<br>part that might fail.
01 Watch
Run salvager watch in any project root. No config, no accounts, no cloud. It starts observing the tree immediately.
02 Capture every revision
Each time a file changes, whether your keystrokes or an agent's edit, Salvager saves a new per-file revision into .salvager/. Automatically. Continuously.
03 Discover the history
Browse the timeline for any file from the CLI, over MCP, or by hand with plain ls and cat. Every version is there, with a content signal to tell them apart.
04 Restore
Roll any file back to any earlier revision. The restore itself first saves the current state, so it's reversible too. Nothing is ever a dead end.
What it protects
The work your safety net was never built to catch.
Watch an agent do real work, destroy it, and find that git, the filesystem,<br>and the editor have nothing to give back. Then watch Salvager hand it over,<br>unharmed. Pick a case.
git / uncommitted dataset / csv document multi-file refactor
claude — agent session — stats.py<br>Recovering uncommitted work that git reset --hard destroyed.<br># extend stats.py with a median() helper and tests<br>● Added median() to stats.py with a test. Running them.<br>$ python -m pytest -q<br>⎿ .. [100%]<br>⎿ 2 passed in 0.02s<br>$ git status --short<br>⎿ M stats.py<br>⎿ M test_stats.py<br>● median() works and the tests pass. Not committed yet.
● Tidying the working tree before the next change.<br>$ git reset --hard HEAD<br>⎿ HEAD is now at 2f4a567 Initial: mean() with tests<br>$ grep median stats.py<br>⎿ (no match)<br>✗ median() is gone from disk. It was never staged.<br>● Trying to get it back through git.<br>$ git reflog<br>⎿ 2f4a567 HEAD@{0}: reset: moving to HEAD<br>⎿ 2f4a567 HEAD@{1}: commit (initial): Initial: mean() with tests<br>$ git fsck --unreachable --lost-found --no-reflogs<br>⎿ (no output)<br>✗ git never hashed the edit, so it has nothing to restore.
● Salvager watches the filesystem. It saved the edit the moment it hit disk.<br>$ salvager history stats.py<br>⎿ TIMESTAMP HASH LABEL LINES ΔLINES START<br>⎿ 2026-06-17 13:17:37 7beb3994 modify 10 +8 def mean(values):<br>⎿ 2026-06-17 13:17:36 0bb06820 first-seen 2 +2 def mean(values):<br>⎿ restore with: salvager restore stats.py<br>⎿ (timestamps below are human-readable; raw ms:)<br>⎿ 1781695057720 modify<br>⎿ 1781695056217 first-seen<br>● The modify revision holds median(). Restoring it.<br>$ salvager restore stats.py 1781695057720<br>⎿ restored stats.py to revision 1781695057720<br>⎿ previous state saved as pre-restore revision 1781695072591 (undo with: salvager restore stats.py 1781695072591)<br>$ python -m pytest -q<br>⎿ 2 passed in 0.02s<br>✓ median() is back, byte for byte. Salvager had it the whole time.
claude — agent session — customers.csv<br>Recovering an untracked dataset after an in-place overwrite dropped most rows.<br># clean customers.csv: drop rows with any blank cell<br>● Loading the dataset to check its shape.<br>$ python -c "import pandas as pd; print(pd.read_csv('customers.csv').shape)"<br>⎿ (10432, 8)<br>● 10,432 rows, 8 columns. Running the cleaning script.
$ python clean.py<br>⎿ wrote customers.csv<br>$ wc -l customers.csv<br>⎿ 313 customers.csv<br>✗ The clean step dropped every row with any blank cell. 10,120 rows gone.<br>● The script overwrote the file in place. customers.csv is generated data — never committed.<br>$ git status --short<br>⎿ ?? customers.csv<br>$ git checkout -- customers.csv<br>⎿ error: pathspec 'customers.csv' did not match any file(s) known to git<br>✗ git never had a version to restore — the file was only ever untracked working data.
● Salvager captured the file before the overwrite. Checking its history.<br>$ salvager history customers.csv<br>⎿ TIMESTAMP HASH LABEL LINES ΔLINES START<br>⎿ 2026-06-17 13:20:35 217a0601 modify 313 -10120 id,email,signup_date,country,plan,mrr,seats,last_seen<br>⎿ 2026-06-17 13:20:33 73ee4895 first-seen 10433 +10433...