claude-oops 0.3.2

Automatic safety net for Claude Code: snapshot before risky ops, restore in seconds.
claude-oops-0.3.2 is not a library.

claude-oops

An undo button for Claude Code.

claude-oops takes a git-stash-style snapshot of your working tree before risky operations and restores it in two seconds when something goes wrong. Snapshots are stored as git refs — local, free, and gone the moment you delete the repo.

$ claude-oops list
┌─────────┬────────────┬───────────────┬───────┬─────────────────────────┐
│ ID      │ AGE        │ TRIGGER       │ FILES │ MESSAGE                 │
╞═════════╪════════════╪═══════════════╪═══════╪═════════════════════════╡
│ abc123f │ 2 min ago  │ pre-bash      │ +0/-12│ rm -rf node_modules     │
│ def4561 │ 18 min ago │ pre-edit      │ +3/-1 │ src/auth.rs             │
│ ghi789a │ 1h ago     │ session-start │ —     │ Started: "fix login"    │
│ jkl012b │ 3h ago     │ manual        │ —     │ before refactor         │
└─────────┴────────────┴───────────────┴───────┴─────────────────────────┘

$ claude-oops to abc123f --force
restored to abc123f

Why

Everyone running Claude Code on a real codebase has, at least once, watched it helpfully clean up a directory by removing files they needed. git stash only covers tracked files; uncommitted untracked work is lost. git reflog only helps if Claude already committed. claude-oops snapshots the entire working tree — tracked plus untracked, respecting .gitignore — at the moments where things tend to go wrong.

Install

cargo install claude-oops
claude-oops install   # patches ~/.claude/settings.json with hooks

After install, every Claude Code session will:

  • snapshot at session start (one baseline);
  • snapshot before each Edit / Write (rate-limited to one snapshot per 2 minutes);
  • snapshot before Bash commands matching dangerous patterns (rm -rf, git reset --hard, find … -delete, etc.).

You can take a manual snapshot at any time:

claude-oops snap -m "before refactor"

Restoring

claude-oops list                 # see what you have
claude-oops show abc123f         # which files would change on restore
claude-oops diff abc123f         # full diff vs the snapshot
claude-oops to abc123f           # restore (asks for confirmation)
claude-oops to abc123f --force   # skip the confirmation prompt
claude-oops to abc123f -- src/auth.rs   # restore one file only
claude-oops to abc123f -- src/          # restore a subtree only

Per-file restore leaves everything else in your working tree alone — useful when only one file went sideways. Files that exist in your working tree but not in the snapshot are deleted (that's the snapshot's state for that path).

Inside a Claude Code session, type /oops instead — the slash command runs claude-oops list and helps you pick a snapshot to restore without switching to a terminal.

to updates the working tree only — your HEAD and commit history stay put. The snapshot itself isn't consumed; you can restore again, or jump to a different one.

Commands

Command Description
snap [-m MSG] Take a manual snapshot (no idempotency check).
list [--json] [--limit N] List snapshots in this repo.
show <id> List the files that would change on restore.
diff <id> Full diff between working tree and snapshot.
to <id> [-f] [-- PATHS] Restore working tree (or specific paths).
drop <id> Delete a snapshot ref + remove from index.
clean Apply retention: keep last 30 OR < 7 days old.
install Add hooks to ~/.claude/settings.json and ship /oops.
uninstall Remove the hooks and /oops we installed.
status Snapshot count, latest, index size.

How it works

  • Storage. Each snapshot is a real git commit object referenced by refs/claude-oops/<id>. Git GC won't reap it because the ref keeps it alive. A JSONL index at .git/claude-oops/index.jsonl records the metadata you see in list.
  • Capture. We don't use git stash create (it ignores untracked files). Instead we build a private temporary index, git add -A into it, and git write-tree — which gives us a tree object containing the entire working state without disturbing your real index.
  • Restore. git read-tree -m -u <tree> then git checkout-index -a -f. HEAD is never moved.
  • Idempotency. Auto-triggered snapshots compare the captured tree SHA against the most recent snapshot's; identical means skip. Manual snap ignores this — if you ask, you get a snapshot.

Limits

  • Requires a git repo. Snapshots outside a git repo are not supported in v0.1 (a tar fallback is on the roadmap).
  • .gitignored files are not captured. If node_modules is in your .gitignore, removing it will trigger a snapshot but the snapshot won't include the directory itself — restore won't bring it back. (npm install will, though.)

Configuration

There is no config file. The defaults are:

  • 2-minute cooldown for Edit/Write snapshots.
  • Retention: keep last 30 OR snapshots from the last 7 days, whichever is more permissive.
  • Settings file location: ~/.claude/settings.json (override via CLAUDE_OOPS_SETTINGS).

Building from source

git clone https://github.com/silverhans/claude-oops
cd claude-oops
cargo install --path .

Quality bar: cargo test, cargo clippy --all-targets -- -D warnings, and cargo fmt --check are all clean on every commit. CI runs them on macOS, Linux, and Windows.

License

MIT.


Show HN: claude-oops — undo button for Claude Code

I built this after the third time Claude Code helpfully cleaned up my repo by removing files I needed. claude-oops takes a git-stash-style snapshot before risky operations and restores in two seconds.

Install: cargo install claude-oops && claude-oops install. From then on every Edit/Write/dangerous Bash gets snapshotted. claude-oops list shows them; claude-oops to <id> restores.

Source: https://github.com/silverhans/claude-oops. Built in Rust, single binary, ~1 MB. Storage is git refs, so it's local, free, and survives reboots.

Happy to take design feedback — particularly on the hook trigger heuristics and the dangerous-bash pattern list.