Fence is an open-source, local-first Rust CLI for reviewing filesystem changes observed during an interactive command session.
fence run -- codex fence run -- claude fence run -- aider fence run -- opencode fence run -- bash
Fence snapshots the worktree before launching the command and again after it
exits. Existing staged, unstaged, and untracked work is part of the before
snapshot, so a later restore does not mean "reset to Git." Fence does not invoke
the git executable, create commits, modify refs, or use Git object storage.
Fence reports session-window changes. It cannot prove which process or person wrote them.
Fence is security-sensitive public-alpha software. Test recovery first in a disposable repository, preserve the store when recovery is uncertain, and do not run Fence with elevated privileges merely to bypass a permission refusal. The implemented safety-first subset is:
- lossless Unix-byte and Windows-WTF-16 path records;
- immutable BLAKE3-addressed, Zstandard-compressed raw-byte objects;
- versioned CBOR manifests and session records;
- content-addressed per-session inclusion policy freezing global excludes,
common
info/exclude, in-tree ignore bytes, tracked overlays, and repository boundaries; - read-only Git discovery, index capture, tracked-path and ignore awareness via
gix; - stable before/after capture around inherited interactive terminal or console streams;
- Unix signal forwarding with an after-capture attempt after interruption;
- stale-session abandonment after the inherited child lock is free;
- path-level diffs with unique exact-content rename detection;
- bounded, control-character-sanitized unified text diffs;
- a side-by-side terminal reviewer with narrow-layout fallback and confirmed file-level restore actions;
- structured three-state restoration planning;
- bounded inverse three-way text merge with structured overlap conflicts;
- single-path regular-file, symlink, and empty-directory restore when safety is proven;
- preview-token-bound whole-session restore of all unambiguous included paths, with staged outputs and a recoverable multi-path journal;
- exact raw-index restore when the current index still equals the session-end index (split indexes are refused);
- byte-verified rollback of interrupted file, index, and pre-commit batch transactions, plus roll-forward cleanup after a verified batch commit point;
- refusal when current bytes, type, symlink target, mode, HEAD, or repository operation state is ambiguous;
- storage integrity checking and reachability-based garbage collection.
- experimental native Windows capture/review with handle-relative no-follow traversal, protected per-user storage, and kill-on-close child containment; the internal mutation backend remains publicly refused.
Not yet implemented: combining index restoration into the worktree batch, and hunk-level restore. Those cases are refused rather than approximated.
Fence uses Rust 2024 and has an MSRV of Rust 1.85.
npm install -g fence-cli@0.1.0-alpha.2 # or compile the exact prerelease from crates.io cargo install fence-cli --version 0.1.0-alpha.2 --locked fence --version
The npm package name is fence-cli; it installs the fence command. The
unscoped npm name fence belongs to an unrelated project. Alpha npm packages
support GNU/Linux x86-64 and Intel/Apple-silicon macOS. They contain prebuilt
binaries and have no npm lifecycle scripts or installation-time downloader.
See the installation and verification guide for direct
GitHub archives, checksums, build attestations, PATH setup, upgrades, and source
builds. See supported platforms before installing on a
non-Ubuntu Linux distribution. The runtime has no network dependency and does
not require the git executable.
Fence creates its private store lazily on the first fence run; there is no
separate init command. Capture is part of run, not a standalone command.
fence run -- sh -c 'printf "session change\n" > example.txt' fence sessions fence diff <session-id> fence restore <session-id> --file example.txt fence restore <session-id> --file example.txt --yes fence doctor
The first restore command is a nonmutating preview. Conflicts preserve current state and exit with status 4. Work through the disposable-repository quickstart and first recovery example before restoring important files.
The Fence rename is a deliberate pre-alpha compatibility break. The fence
binary does not provide an anchor alias, read ANCHOR_* environment
variables, interpret .anchorignore, auto-discover old Anchor stores, or
migrate them. If a legacy Anchor store is present for the same repository,
fence doctor reports it and mutation/session start refuses; the old data is
left untouched. Opaque pre-alpha wire-domain strings and object magic remain
unchanged where changing them would add no safety and would complicate
forensics.
Run any interactive command from inside a non-bare Git worktree:
fence run -- codex fence sessions fence show <session-id> fence diff <session-id> fence diff <session-id> --current fence diff <session-id> --drift fence diff <session-id> --format json fence review <session-id>
In the reviewer, r exits raw terminal mode and asks for confirmation before
calling the same exact single-path restore service. Exact renames are disabled
as a one-file TUI action because they span two paths.
Restore one worktree-root-relative path:
fence restore <session-id> --file src/main.rs fence restore <session-id> --file src/main.rs --yes fence restore <session-id> --file src/main.rs --merge fence restore <session-id> --file src/main.rs --merge --yes \ --expect-merged <previewed-object-id> fence restore <session-id> --all fence restore <session-id> --all --yes \ --expect-current <previewed-manifest-id> fence rollback <session-id> fence rollback <session-id> --yes \ --expect-current <previewed-manifest-id> fence rollback <session-id> --format json fence restore <session-id> --file src/main.rs --yes --format json fence restore-index <session-id> --yes
The restore either applies a byte-verified inverse, reports that no action is
needed, or exits with a visible conflict. It does not overwrite post-session
drift. Without --yes, exact restore prints the diff-review command and makes
no change. --merge previews a clean, bounded inverse text merge without changing
the worktree and prints its object ID. --merge --yes --expect-merged <id>
recalculates and applies only that exact result. Overlapping edits,
binary/opaque input, and oversized text remain conflicts.
rollback is the clearer alias for restore --all; both first perform a
nonmutating preview. They apply only when every changed
path is unambiguous and --expect-current matches a freshly recaptured whole
worktree manifest. All outputs are staged before any target is evacuated. A
persistent batch journal retains every backup until all targets verify. Fence
refuses a batch that would require reconstructing a missing parent directory;
it does not infer uncaptured structural directories. The index remains a
separate opt-in operation.
Verify retained data and preview garbage collection:
fence doctor fence gc --dry-run fence gc fence recover fence recover-transactions --yes fence delete <session-id> --yes fence deleted-sessions fence undelete <session-id>
fence diff returns 1 when differences exist. Restore conflicts return 4.
The run command returns the wrapped child’s exit code (or 128 + signal on
Unix) after attempting the after-snapshot.
The default diff is before → session end. --current is before → current;
--drift is session end → current and separately reports repository and raw
index drift. fence recover never guesses a missing session end: after it can
acquire the worktree lock, it marks stale nonterminal records Abandoned.
fence recover-transactions --yes is separate: it validates the immutable
restore plan, byte-verifies, and rolls back interrupted single-path/index or
pre-commit batch transactions.
Once every batch target verified, recovery instead finishes backup cleanup
because that state is the durable commit point. Legacy incomplete journals
remain visible but require manual recovery because they lack sufficient state.
Session deletion is recoverable by default. Tombstoned sessions continue to
protect their manifests and objects from garbage collection. fence purge <id> --yes permanently removes only the tombstoned record; a later fence gc
can then reclaim newly unreachable immutable data.
By default Fence includes tracked files and nonignored untracked files. It
excludes Git metadata, its own store, ignored files, and submodule contents. A
root .fenceignore adds Git-style exclusions but cannot re-include a
Git-ignored path. .fenceignore itself is captured.
Fence freezes the selected global Git excludes, common info/exclude, nested
.gitignore, root .fenceignore, case mode, and repository boundaries before
the initial capture. Session-end and current captures use those retained bytes
even if live ignore files later change; drift is shown separately. Older
session schemas remain readable but are review-only because they cannot prove a
complete current-state scope.
Default capture limits are 250,000 included manifest entries, 2 GiB of raw
regular-file content, and 256 MiB per regular file. Regular files, symlinks, and
empty directories all consume the entry ceiling. Exceeding a limit aborts
before the child starts. Ignored files are not a security boundary: a
nonignored .env, credential, or key file is stored exactly like source code.
Command arguments are not retained by default because they commonly contain
tokens and other secrets. Use fence run --record-arguments -- <command> only
when the complete invocation is safe to store. Capture limits and the two
degraded-behavior switches can be configured or overridden explicitly; see
Configuration.
See Safety and threat model, architecture, and storage format before using Fence on sensitive worktrees. Operational failures are covered by the troubleshooting guide, and old Anchor users must follow the migration refusal guide. Automation should follow the schema-1 JSON and exit-status contract rather than parse human output. Independent reviewers can start with the audit guide. Implemented and remaining work is tracked in the implementation roadmap.
Tagged Unix alpha releases provide .tar.gz archives for Linux x86-64 and
macOS x86-64/arm64. Verify the central SHA-256 manifest before installing:
sha256sum --check --ignore-missing fence-0.1.0-alpha.2-SHA256SUMS tar -xzf fence-0.1.0-alpha.2-<target>.tar.gz install fence-0.1.0-alpha.2-<target>/fence ~/.local/bin/fence
On macOS, use shasum -a 256 -c in place of sha256sum --check. GitHub build
provenance can additionally be checked with:
gh attestation verify fence-0.1.0-alpha.2-<target>.tar.gz \ -R 22elix3r/fence
Release owners follow the public alpha release checklist.
| Platform | Capture/review | Filesystem restore |
|---|---|---|
| Linux | Supported | Experimental single-path and batch |
| macOS | Supported | Experimental single-path and batch |
| Windows | Experimental native support | Not claimed; metadata-safe mutation is pending |
Windows does not receive an npm or direct-download artifact in alpha.2.
Linux ARM, Windows ARM, and Alpine/musl are also unsupported. See the complete
platform policy.
Windows paths and command arguments retain exact WTF-16. Capture uses pinned directory handles, 128-bit file identities, reparse-point inspection, and alternate-stream detection. A native no-replace transaction backend exists, but the current manifest records extended-metadata observation as unavailable, so public worktree mutation remains refused until that proof gap is closed. Stores live under the current user's Local AppData and receive a protected current-user/SYSTEM DACL. The wrapped process tree is assigned to a kill-on-close Job object. Windows capture/review remains experimental while its real-runner compatibility matrix grows, especially for non-NTFS volumes, antivirus sharing interference, unusual case-sensitive directories, and third-party console applications.
See CONTRIBUTING.md. Security issues should follow SECURITY.md.
Licensed under either Apache-2.0 or MIT, at your option.