undo logo

undo

Undo what the last shell command did to the filesystem

release
Website GitHub

What is it?

What it is

A CLI tool that reverts the last shell command's changes to the filesystem by intercepting destructive operations and storing session data.

Why it exists

To provide a safety net for accidental file modifications or deletions in the shell without requiring users to change their habits or set up complex backups.

Who should use it

Linux users who frequently use the command lineDevelopers who accidentally delete files or overwrite data via shell commandsSystem administrators looking for a lightweight filesystem safety netUsers who want command-level undo functionality without full filesystem snapshots

Who should avoid it

macOS users (due to System Integrity Protection)Users working with static binaries or Go programs (which bypass LD_PRELOAD)Users requiring secure deletion (the tool preserves copies of overwritten files)Users in sandboxed environments like Snap or FlatpakUsers needing to protect against sudo/setuid programs

How it works

A quick walkthrough in plain English

How undo works

Step 1 of 3

You interact with it

Open undo, send a request, or connect it to your stack.

Features

Command‑granular undo for shell commands
No daemon or background service—only a shell hook
LD_PRELOAD library intercepts destructive libc calls
Hardlinking for deletions (no extra disk for rm -rf)
Copies previous content for overwrites (truncation, chmod)
Per‑command session storage in ~/.local/share/undo
Automatic ignore of common build noise (node_modules, .cache, .git)
Configurable limits (UNDO_KEEP, UNDO_MAX_STORE, UNDO_MAX_BYTES)
Undo/redo, diff, list, apply, purge, gc, doctor, upgrade, uninstall commands
Supports bash, zsh, fish via shell hooks
Can arm a single command with undo run without installing a hook

Advantages

  • Provides a safety net for accidental deletions and overwrites without requiring aliases or filesystem snapshots
  • Works out of the box on any Linux glibc system (amd64/arm64) with no root privileges
  • Hardlinking makes rm -rf instant and free in terms of additional disk usage
  • Per‑command granularity allows precise rollback of only the intended changes
  • No daemon means no background resource usage or persistence issues
  • Configurable ignore patterns reduce noise from build scripts
  • Undo store is bounded in size and number of sessions, preventing runaway disk usage
  • Undo can be used in CI or scripted environments via undo run
  • Provides diagnostic tools (doctor, diff, list) for transparency

Disadvantages

  • Only works on Linux glibc; not supported on macOS, Windows, or musl without building from source
  • Cannot intercept static binaries, setuid programs, or raw syscalls (e.g., Go programs)
  • Does not catch writes through already‑open file descriptors, mmap, ftruncate, or chown changes
  • Hardlinking does not free space until prune, so large deletions temporarily consume disk until purge
  • Overwrites still consume disk space up to UNDO_MAX_BYTES per file
  • Requires shell hook or manual arm; may need manual rc modifications
  • Not a full backup solution—only a safety net; important data should still be backed up
  • Undo may not capture operations performed outside the hooked shell

Installation

native

Get the binary:
  - One-liner: curl -fsSL https://undo.edaywalid.com/install.sh | sh
  - Homebrew (Linux): brew install edaywalid/tap/undo
  - Debian/Ubuntu: download .deb from releases and run sudo dpkg -i undo_*.deb
  - Fedora/openSUSE: download .rpm from releases and run sudo rpm -i undo_*.rpm
  - Arch: yay -S undo-cli-bin (AUR) or download .pkg.tar.zst from releases
  - Nix: nix run github:edaywalid/undo (flake, experimental)
  - From source: make install

Then, turn on the hook for your shell (see README for exact line based on your shell and installation method).
Finally, run 'undo doctor' to verify.

FAQ

How does undo actually capture and restore destructive filesystem changes?

undo hooks every command with an LD_PRELOAD library that intercepts libc calls such as unlink, rename, open with write flags, rmdir, mkdir, chmod, etc. Before a destructive operation is performed, the library hard‑links the target file (or copies it for in‑place overwrites) into a per‑command session directory and records a journal line. When you run `undo`, it reads the last session’s journal and replays the actions in reverse: relinks deleted files, moves renamed files back, swaps truncated files with their backups, removes accidental creations, and restores original modes. No snapshots or daemons are needed; everything is stored in plain files under `~/.local/share/undo/sessions/`.

What files or directories does undo ignore by default, and how can I change that?

By default undo skips common build noise such as `node_modules`, `.cache`, `__pycache__`, and `.git`. It also collapses repeated writes to the same file within one command. To customize, edit `~/.config/undo/ignore` with glob patterns (e.g., `dist`, `/home/you/scratch`). You can disable the built‑in ignores with `UNDO_DEFAULT_IGNORE=0` or override the list entirely with `UNDO_IGNORE` (colon‑separated). The ignore file is read before any command is executed.

How much disk space does undo consume and how can I control it?

Undo stores backups in a per‑command session directory. Deletions are hard‑linked, so they use no extra space. In‑place overwrites (e.g., `>`, editors, `shred`) copy the original file, capped at `UNDO_MAX_BYTES` (256 MiB by default). The total store size is limited by `UNDO_MAX_STORE` (1 GiB default) and the number of kept sessions by `UNDO_KEEP` (30). Oldest sessions are pruned automatically; the most recent session is never dropped. Run `undo gc` to prune on demand or `undo purge` to delete all backups.

Can undo recover files deleted by `sudo`, static binaries, or programs that bypass libc?

No. `LD_PRELOAD` only works for dynamically linked programs that go through libc. Undo does not see static binaries, set‑uid programs, or raw syscalls (e.g., Go programs). It also cannot capture writes through already‑open file descriptors, `ftruncate`, or mmap writes. For such cases, use `undo run` to arm a single command or rely on external backups.

How do I use undo in a script or CI environment where the shell hook isn’t automatically loaded?

You can arm a single command with the shim using `undo run -- <command>`. This temporarily sets `LD_PRELOAD` for that command without modifying your shell rc. For scripted installs, set `UNDO_MODIFY_RC=1` to let the installer add the hook line automatically, or `UNDO_NO_MODIFY_RC=1` to skip it. In CI, you can source the hook manually or use `undo run` for the specific commands you want protected.

Loading documentation…
View on GitHub

Featured in Videos

YouTube tutorials and walkthroughs for undo

Alternatives

Similar projects ranked by category, topics, and text overlap.

Compare
undo | MushyBook