CLI tool
edaywalid/undo avatar
edaywalid/undo

undo: A shell hook that journals filesystem calls so you can revert the last command

Project brief: Undo what the last shell command did to the filesystem. Hook. A shell hook (zsh, bash, fish) arms a small LD_PRELOAD library, libundo.so, around every command you run.

396 stars13 forksGoMIT

At a glance

What is it?
undo wraps every shell command with an LD_PRELOAD shim that logs destructive filesystem operations, then replays them in reverse. It is command-granular, not snapshot-based, and works without root on any filesystem.
Who is it for?
Adopt undo if you routinely run risky commands like rm -rf, mv, or build scripts that can clobber files, and you want a safety net that requires no habit changes or filesystem snapshots. Skip it if you need to recover data after a reboot or across sessions, or if you run on musl-based distros, macOS, or WSL without a custom build.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 35 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What problem undo solves and who it is for

The shell has never had a built-in undo, and the typical advice is to alias rm to a trash command or set up btrfs/zfs snapshots before an accident. Those approaches require you to change your habits ahead of time. undo asks for nothing: you keep typing rm, and the safety net is already there, even when the deletion happens three processes deep inside a build tool where no alias could reach. It is command-granular, not time-granular, so it reverts exactly what that one command touched, on any filesystem, without root. That is the difference from a snapshot, which rolls the whole tree back to a point in time and takes your good work with it. The tool is aimed at developers and system administrators who run destructive commands interactively or in scripts, and who want a per-command undo without setting up a trash alias or a CoW filesystem.

The mechanism: LD_PRELOAD journaling, not snapshots

The core is a small shared library, libundo.so, armed by a shell hook for zsh, bash, or fish. While armed, the library intercepts destructive libc calls: unlink, rename, open with write flags, rmdir, mkdir, chmod, and others. Before each call goes through, it saves the affected file into a per-command session and appends a line to a journal. Deletions are saved by hardlink, so rm -rf on gigabytes copies no data and costs almost nothing. Each command that changed something gets one session directory under ~/.local/share/undo/sessions, mode 0700. The session contains a cmd file with the command line, a journal with one line per change, pid and done markers so undo will not touch a running command, and a budget file tracking bytes stored. The data directory holds hardlinks to the original files. When you run undo, it reads the last session's journal and replays it in reverse: relinks deleted files, moves renames back, swaps truncated files with their backups, removes accidental creations, and recreates directories with their original modes. There is no daemon and nothing runs between your commands, so the overhead is limited to the interception itself.

Installation and setup: from one-liner to package managers

The fastest install is a one-liner: curl -fsSL https://undo.edaywalid.com/install.sh | sh. That installs into ~/.local and then asks whether to add the hook line to your shell rc, showing the exact line first. If there is no terminal to ask at, such as a CI run, it never edits anything. Set UNDO_MODIFY_RC=1 to answer yes for scripted installs, or UNDO_NO_MODIFY_RC=1 to answer no. Package managers are also supported: Homebrew on Linux via brew install edaywalid/tap/undo, Debian/Ubuntu via a .deb, Fedora/openSUSE via an .rpm, Arch via yay -S undo-cli-bin, Nix via nix run github:edaywalid/undo, and from source with make install, which needs gcc and go. After a package install, you add the hook line manually. For zsh, append source /usr/share/undo/undo.zsh to ~/.zshrc; for bash 5 or newer, source /usr/share/undo/undo.bash; for fish 3.4 or newer, source /usr/share/undo/undo.fish. The hook directory differs by install method: distro packages use /usr/share/undo/, the one-liner and make install use ~/.local/share/undo/, and Homebrew uses $(brew --prefix)/share/undo/. For Homebrew, use double quotes when echoing the source line so the prefix expands once; leaving it unexpanded runs brew on every shell startup, and that is a Ruby program, so it is slow.

Usage: undo, redo, diff, and the single-command shim

Once the hook is active, you run undo on its own line to revert the most recent command that changed files. The README warns not to chain it with &&, because a chained line is a single command to the shell, so undo would be running inside the very session it is being asked to revert, before that command has finished. You can also run undo -i to pick a session and cherry-pick individual entries to restore, undo redo [id] to re-apply an undone session, and undo diff [id] to show what a session changed with content diffs. undo list shows recent sessions newest first, and undo show [id] details what a session changed. For script or CI contexts without a hook, you can arm a single command with undo run -- ./sketchy-cleanup.sh. The undo doctor command verifies the install and then actually deletes and restores a canary file, so you get a real answer rather than a guess. The storage format is plain files you can inspect, which is a nice property for debugging or manual recovery.

Storage, budget, and what it costs you

undo keeps the last 30 sessions within a 1 GiB budget, both configurable, and undo purge wipes the store. The most recent session is always kept, even if it alone exceeds the budget, since that is the one undo reverts. Deletions are saved by hardlink, so rm -rf on large files copies no data and costs almost nothing in disk space. However, hardlinks only work on the same filesystem, so if you delete a file that lives on a different mount point, undo cannot hardlink it and may have to copy the data, which could blow the budget for huge files. Also, the budget is shared by every process in the session, so a build script that touches thousands of files could quickly consume the 1 GiB cap, and older sessions may be evicted. The session directory is mode 0700, so other users on the system cannot read your command history or file contents, which is a reasonable privacy measure.

Limitations: what undo cannot catch

The README has a section titled What it cannot catch, which is a clear admission of boundaries. Since the shim intercepts libc calls, any program that uses raw syscalls or a different libc may bypass it. For example, statically linked binaries or programs that call syscall() directly will not be journaled. Also, undo cannot protect against secure deletion tools like shred, because shred overwrites the file contents in place, and the original data is gone before any journal entry can help. The README explicitly says to read the secure deletion section before using shred. Another limitation is that undo only works on Linux with glibc 2.6 or newer, on amd64 or arm64. musl-based distros, macOS, and WSL are not supported out of the box, and the README points to a platform support section for details, which we do not have. So if you are on Alpine or a Mac, this tool will not work without a custom build. Finally, undo is session-based: it only reverts the last command that changed files. If you run multiple commands and then realize a mistake two commands back, you have to undo the most recent one first, then the one before, which could be tedious.

Alternatives: trash aliases and filesystem snapshots

The most common alternative is to alias rm to a trash command, such as trash-cli or gvfs-trash. That approach moves deleted files to a trash directory, from which you can restore them later. The difference is that a trash alias only catches files deleted by that alias; it does not catch deletions made by build tools or scripts that call rm directly, and it does not catch mv overwrites, truncations, or chmod changes. undo intercepts at the libc level, so it catches those regardless of how the command is invoked, even three processes deep. Another alternative is to use filesystem snapshots, such as btrfs or zfs snapshots. Snapshots roll the entire filesystem back to a point in time, which can restore deleted files but also reverts unrelated changes made after the snapshot. undo is command-granular, so it only reverts what that one command touched, leaving your other work intact. However, snapshots require setting up a CoW filesystem ahead of time, which is a bigger change than installing a shell hook. undo is also different from version control systems like git, which only track files you explicitly add and commit, and do not catch untracked files or build artifacts.

Maintenance and upgrade cost

The project is actively maintained, with recent releases v0.3.0 in August 2026 and earlier v0.2.9 and v0.2.8. The README includes sections on Upgrading and Uninstalling, though the details are truncated. The install methods suggest that upgrades are handled through the same channels, such as brew upgrade or downloading a new .deb or .rpm. Since the storage format is plain files, an upgrade should not corrupt existing sessions, but it is wise to check the release notes for any format changes. The license is MIT, which is permissive for commercial use, but you should read the LICENSE file for exact terms. The project is written in Go, which produces a statically linked binary, but the LD_PRELOAD library is likely C, so you need gcc to build from source. The maintenance cost is low for end users: you just update the binary and keep the hook line in your rc. The main ongoing cost is the disk space used by the session store, which you can control with the budget and purge commands.

Editorial conclusion

Adopt undo if you routinely run risky commands like rm -rf, mv, or build scripts that can clobber files, and you want a safety net that requires no habit changes or filesystem snapshots. Skip it if you need to recover data after a reboot or across sessions, or if you run on musl-based distros, macOS, or WSL without a custom build. Before relying on it, verify that your shell hook is loaded (run undo doctor), understand the 30-session and 1 GiB budget limits, and confirm that your filesystem supports hardlinks, since undo uses them to avoid copying data. Also read the secure deletion section if you ever run shred, because undo cannot protect against it.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Community notes

Community notes