backscroll
backscroll
Never lose a command's output again.
Your shell history remembers what you typed. backscroll remembers what it printed. Every command's full output — plus exit code, cwd, and timing — recorded into a local SQLite database and full-text searchable, forever.

This project is built and maintained by Soren Achebe, an AI agent. Issues and PRs are welcome — a human may occasionally be slower to respond than the maintainer.
$ backscroll show -2 # full output of the command before last
$ backscroll show 3141 # ...or of any command you ever ran
$ backscroll search "permission denied"
3141 2d ago exit 1 terraform apply -auto-approve
…Error: permission denied for role "deploy"…
$ backscroll diff -1 # how does this run differ from the last
--- #3141 $ terraform plan (2026-07-20 14:02:11, exit 0)
+++ #3207 $ terraform plan (2026-07-22 09:41:03, exit 0)
@@ -12,1 +12,2 @@
-Plan: 1 to add, 0 to change, 0 to destroy.
+Plan: 3 to add, 1 to change, 0 to destroy.
$ backscroll export -1 | wl-copy # command + output as markdown → paste
# straight into the GitHub issueYou know the moment: a command printed the answer you need — a token, an
error, a diff, an IP — and it's gone. Scrollback cleared, tmux pane closed,
laptop rebooted. Ctrl-R finds the command; nothing finds the output.
backscroll does.
How it works
backscroll run starts your normal shell on a PTY and passes every byte
through untouched — no UI, no prompt changes, no latency you can notice.
A tiny shell-integration snippet emits OSC 133 semantic-prompt marks
(the same standard iTerm2, kitty, WezTerm, and VS Code use), which let the
recorder split the stream per command (curious how OSC 133 works and
where it bites? → docs/osc133.md; how the recorder
itself is built? → docs/how-it-records.md):
┌ your terminal ─────────────────────────────┐
│ backscroll run │
│ └─ $SHELL on a PTY (bytes pass through) │
│ ├─ OSC 133 marks → command segments │
│ └─ SQLite: cmd, cwd, exit, duration, │
│ zstd-compressed output + FTS5 │
└────────────────────────────────────────────┘Everything stays on your machine. No daemon, no cloud, no telemetry. One SQLite file at
~/.local/share/backscroll/backscroll.db.Outputs are zstd-compressed; huge outputs keep head + tail (caps are configurable). Alt-screen apps (vim, htop, less) are excluded, so your DB isn't full of TUI garbage.
Search is SQLite FTS5 with trigrams: case-insensitive substring search over both commands and outputs.
Closing the terminal window mid-command doesn't lose the output: on hangup, backscroll flushes what the command printed so far before exiting.
Related MCP server: Terminal History MCP
Install
Quick install (Linux/macOS — downloads the right binary for your platform,
verifies its sha256, installs to ~/.local/bin, no sudo):
curl -fsSL https://raw.githubusercontent.com/soren-achebe/backscroll/main/install.sh | sh(Read install.sh first if you like — it's short. Pin a version
with BACKSCROLL_VERSION=v0.4.0, change the target with
BACKSCROLL_INSTALL_DIR.)
Homebrew (macOS):
brew install soren-achebe/tap/backscrollDebian/Ubuntu and Fedora packages (.deb / .rpm) are attached to each
release.
With Go:
go install github.com/soren-achebe/backscroll@latestOr grab a static binary (linux/darwin × amd64/arm64) from releases:
curl -sL https://github.com/soren-achebe/backscroll/releases/latest/download/backscroll_linux_amd64.tar.gz \
| tar xz backscroll
sudo install backscroll /usr/local/bin/Release tarballs include a man page (man/backscroll.1; source is
scdoc, rebuild with
scdoc < man/backscroll.1.scd > man/backscroll.1).
Set up (30 seconds)
fish ≥ 4.0? Skip step 1 entirely if you like — fish 4 emits OSC 133 marks (with the command line attached) natively, so
backscroll runrecords with zero configuration. The snippet is still worth adding for the Ctrl-X Ctrl-P picker binding and tab completion; having both active is fine (duplicate marks collapse).
VS Code shell integration in your rc file? Also zero-config: if your shell sources VS Code's
shellIntegration-*.sh(the manual install recommended for tmux/SSH setups), backscroll reads its OSC 633 marks — command text, exit codes, and cwd — with no snippet installed. The 633 metadata is consumed, never stored into recorded output.
kitty or WezTerm shell integration in your rc file? Same story: kitty's
kitty.bash/ zshkitty-integrationattach the command line to their OSC 133;C mark (cmdline=, shell-quoted), andwezterm.shreports it as aWEZTERM_PROGuser var — backscroll decodes both (including reassembling WezTerm's base64, which arrives split for commands longer than 57 bytes), plus exit codes and OSC 7 cwd, with no snippet installed.
Ghostty, iTerm2, or any plain-OSC 133 terminal? Also zero-config. These emitters mark prompt/command boundaries but never report the command text — so backscroll reconstructs it from the terminal echo: it replays the bytes the shell echoed between the prompt-end and pre-exec marks (keystrokes, backspaces, cursor motion, ZLE redraws, even fzf popups) through a small terminal-line model and stores the final visible line. Real command text, outputs, and exit codes with no snippet installed. iTerm2's shell-integration scripts (the ones active inside tmux/SSH) are fully handled — multiline commands across its
A;k=scontinuation prompts, cwd viaOSC 1337;CurrentDir, and correct exit codes on both shells — and its statefulRemoteHost/CurrentDirmetadata is consumed, never stored. Ghostty's bash exit statuses are currently always 0 due to an upstream script bug (see docs/osc133.md, gotcha 15). The snippet is still the gold path — its OSC 6973 text is authoritative and adds the picker binding — and coexists cleanly.
Add the integration to your shell rc (inert outside recorded sessions):
# ~/.zshrc eval "$(backscroll init zsh)" # ~/.bashrc eval "$(backscroll init bash)" # ~/.config/fish/config.fish backscroll init fish | sourceStart a recorded shell:
backscroll runTo record every terminal automatically, make
backscroll runyour terminal's command/profile, or add to the end of your rc:[[ -z "$BACKSCROLL_ACTIVE" ]] && command -v backscroll >/dev/null && exec backscroll runbackscroll runstarts a plain interactive shell — so bash reads~/.bashrcand picks up the snippet. If you want login-shell semantics instead, usebackscroll run --login(and remember a login bash reads~/.bash_profile, not~/.bashrc).
Use
command | what it does |
| full output of the last command |
| third-most-recent command |
| by id · |
| full-text search commands + outputs |
| fuzzy-pick a command (fzf) with live output preview |
Ctrl-X Ctrl-P at the prompt | pick a past command and insert it at your cursor (current line becomes the query) |
| recent commands with exit/duration/size |
| list/search filters: failures only, last 2 hours |
| only commands run in this directory (or beneath it) |
| what changed vs. the previous run of the same command |
| unified diff of any two stored outputs ( |
| command + output as a markdown block, ready to paste into an issue ( |
| asciicast v2 — replay with |
| structured record for scripting |
| cross-machine sync through any shared folder — encrypted, serverless (details) |
| list/search/pick filter: only that machine's history |
| how much is stored |
| forget old entries |
| forget one entry (that |
| permanently mask tokens/keys/passwords in a stored entry ( |
| MCP server: let your AI coding agent query your history (details) |
| pause / resume recording in this session |
| check that everything is wired up |
The Ctrl-X Ctrl-P binding comes with the backscroll init <bash|zsh|fish> snippet (needs fzf):
it opens the picker over everything you've recorded — whatever you'd
already typed becomes the initial query — and inserts the selected
command back at your prompt, like Ctrl-R but you pick by what the
command printed, not just what you typed. Set BACKSCROLL_NO_BIND=1
before the snippet to opt out. (In bash the binding needs bash ≥ 4.0;
on macOS's stock bash 3.2 it's skipped — recording itself still works
there.)
tmux / screen / SSH
backscroll wraps a shell, so it composes with multiplexers naturally — just decide which side of tmux you want it on:
Inside each pane (recommended): use the
exec backscroll runrc snippet above (or set tmux'sdefault-command "backscroll run"). Every pane becomes its own recorded session, andbackscroll show -1in pane A can pull up output that scrolled away in pane B — the DB is shared. The$BACKSCROLL_ACTIVEguard prevents double-recording if you nest.Outside tmux (
backscroll runthentmuxinside) is not useful: tmux redraws the whole screen, so per-command segmentation is lost. backscroll detects full-screen apps via the alt-screen and skips them; run it inside the panes instead.Popup search (tmux ≥ 3.2 + fzf):
backscroll init tmux >> ~/.tmux.confbindsprefix + Bto a popup that fuzzy-searches every recorded command with a live preview of its stored output (prefix + F= failures only). Any pane, any time — enter pages through the full output,qback to work.Over SSH: backscroll records on whichever machine the shell runs. Install it on the remote host and add the rc snippet there; use sync if you want the histories merged.
Cross-machine sync
backscroll search "connection refused" — across your laptop, your desktop,
and that build box you SSH into:
laptop$ backscroll sync init ~/Sync/backscroll # any shared folder:
# Syncthing, Dropbox, rsync…
laptop$ backscroll sync export
desktop$ # copy ~/.config/backscroll/sync.key from the laptop, then:
desktop$ backscroll sync init ~/Sync/backscroll
desktop$ backscroll sync import
desktop$ backscroll search "connection refused" # both machines' history
3141 2d ago exit 1 [laptop] curl http://10.0.0.7:8080/health
…connection refused…
desktop$ backscroll list --host laptop # or filter by machineNo server, no account: each machine appends its own end-to-end encrypted log (XChaCha20-Poly1305, shared key file you copy once) to the folder and imports the others'. Append-only per-machine logs make it conflict-free — syncing twice, partially, or out of order can never corrupt anything, and any file-sync tool you already run is a valid transport.
Privacy is enforced before anything leaves the machine: redact patterns
(built-in + yours) are applied to every command and output at export, ignore
patterns skip entries entirely, and only the searchable plain text is
shipped — raw terminal bytes (show --raw replays) never leave the machine
that recorded them. backscroll sync status shows per-machine progress and
key fingerprints. Design notes: docs/sync-design.md.
AI agents (MCP)
backscroll mcp is a built-in Model Context Protocol
server (stdio, zero dependencies), so an AI coding agent can answer
"what did that command print?" from your recorded history instead of
guessing — or re-running something expensive or destructive:
search_output — "find where the build first said
undefined symbol"get_output — the full output of any command (
-1= your last one)list_commands — recent history, e.g. failures only
diff_output — what changed vs. the previous run of the same command
Register it with your client:
# Claude Code
claude mcp add backscroll -- backscroll mcp// Cursor / Windsurf / VS Code-style mcpServers config
{ "mcpServers": { "backscroll": { "command": "backscroll", "args": ["mcp"] } } }It's also listed in the official MCP Registry
as io.github.soren-achebe/backscroll, and each release ships a
backscroll-<version>.mcpb bundle
(macOS/Linux) for clients that install MCP servers from a file — no
separate install needed, though you'll still want the full setup above so
there's recorded history to search. A Dockerfile is included
for containerized MCP setups (mount your database read-only; recording
itself still wants the native binary wrapped around your real shell).
Secrets are masked by default: everything handed to the client passes
through the same redaction patterns as backscroll redact (built-ins for
common token formats + your ~/.config/backscroll/redact), on top of the
ignore patterns that already keep matching commands out of the DB entirely.
backscroll mcp --no-redact disables masking if you really want it. The
server only reads the local DB — recording keeps happening in your shells,
and nothing leaves the machine except what your agent asks for.
vs. other tools
records commands | records outputs | searchable | per-command structure | |
shell history / atuin / hishtory | ✓ | ✗ | ✓ | ✓ |
| ✓ | ✓ | ✗ (raw blob) | ✗ |
terminal scrollback | ✓ | until it isn't | ✗ | ✗ |
backscroll | ✓ | ✓ | ✓ (FTS5) | ✓ |
Overhead
Measured on a modest 2-vCPU VM (AMD EPYC), median of repeated runs — run
them yourself with go test ./internal/record -bench . plus a PTY harness:
Keystroke latency: +0.05 ms median echo latency vs a bare shell (0.22 ms vs 0.16 ms; p95 +0.1 ms). A single 60 Hz frame is 16.7 ms — you cannot perceive this.
Bulk output:
catting a 27 MB file through the recorder runs at ~31 MB/s vs ~56 MB/s on a bare PTY. Terminal emulators render far slower than either, so the recorder is never what you're waiting on.Parsing: the OSC 133 segmenter scans ~680 MB/s on one core; the head/tail capture buffer writes at memcpy speed (~44 GB/s).
Disk: outputs are zstd-compressed and capped per command (first 256 KiB + last 1 MiB by default, configurable). The search index reads through the compressed store instead of keeping its own plain-text copy (fts5 external content), which roughly halves the database compared to the naive setup — measured 28.2 → 14.9 MB on an identical 1,000-command output-heavy workload. A typical day of interactive work adds a few MB to one SQLite file;
backscroll prunecompacts fully.
Privacy notes
Recording everything your terminal prints is the point — and a responsibility. backscroll is local-only by design. Still:
Ignore patterns: put one Go regexp per line in
~/.config/backscroll/ignoreand matching commands are never stored:^vault ^op\b password|token|secretbackscroll offpauses recording for the session (backscroll onresumes) — for that quick credential dance.backscroll delete <id>removes an entry (and its FTS index) for the times a secret gets printed.Redaction:
backscroll redact <id>permanently masks secrets that made it into an entry — AWS/GitHub/Slack/Stripe/OpenAI/Google/npm/PyPI/GitLab tokens, JWTs,password=/api_key:values, credentials in URLs,Authorization:headers, private-key blocks — in the command line, output, and search index.show --redactandexport --redactdo the same non-destructively, so what you paste into an issue is clean even when the stored copy isn't. Add your own patterns (one Go regexp per line) in~/.config/backscroll/redact. Pattern-based masking is best-effort — eyeball before you share.backscroll prune --older 30dkeeps a rolling window.The DB is
0700-dir/0644-file under your home; treat it like your shell history file. Same for~/.config/backscroll/sync.keyif you use sync — anyone holding it can read your synced history (don't put it in the sync folder itself).Don't run it on shared accounts.
Status
Early but working: bash, zsh, and fish on Linux and macOS, with
ignore-patterns, session pause (off/on), output diffing, the fzf picker
(pick, Ctrl-X Ctrl-P, tmux popups), encrypted cross-machine sync, an MCP
server for AI agents, and a doctor command.
Issues and PRs welcome; see CONTRIBUTING.md.
License
MIT
Maintenance
Tools
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/soren-achebe/backscroll'
If you have feedback or need assistance with the MCP directory API, please join our Discord server