Skip to main content
Glama

backscroll

CI Release Go Reference License: MIT

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.

demo

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 issue

You 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/backscroll

Debian/Ubuntu and Fedora packages (.deb / .rpm) are attached to each release.

With Go:

go install github.com/soren-achebe/backscroll@latest

Or 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 run records 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 / zsh kitty-integration attach the command line to their OSC 133;C mark (cmdline=, shell-quoted), and wezterm.sh reports it as a WEZTERM_PROG user 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=s continuation prompts, cwd via OSC 1337;CurrentDir, and correct exit codes on both shells — and its stateful RemoteHost/CurrentDir metadata 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.

  1. 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 | source
  2. Start a recorded shell:

    backscroll run

    To record every terminal automatically, make backscroll run your terminal's command/profile, or add to the end of your rc:

    [[ -z "$BACKSCROLL_ACTIVE" ]] && command -v backscroll >/dev/null && exec backscroll run

    backscroll run starts a plain interactive shell — so bash reads ~/.bashrc and picks up the snippet. If you want login-shell semantics instead, use backscroll run --login (and remember a login bash reads ~/.bash_profile, not ~/.bashrc).

Use

command

what it does

backscroll show

full output of the last command

backscroll show -3

third-most-recent command

backscroll show 3141

by id · --raw keeps colors

backscroll search <text>

full-text search commands + outputs

backscroll pick

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)

backscroll list -n 50

recent commands with exit/duration/size

... --exit fail --since 2h

list/search filters: failures only, last 2 hours

... --cwd .

only commands run in this directory (or beneath it)

backscroll diff 3141

what changed vs. the previous run of the same command

backscroll diff -2 -1

unified diff of any two stored outputs (-U n context)

backscroll export -1

command + output as a markdown block, ready to paste into an issue (--details folds it)

backscroll export 3141 --format cast

asciicast v2 — replay with asciinema play

backscroll export -1 --format json

structured record for scripting

backscroll sync init ~/Sync/bks

cross-machine sync through any shared folder — encrypted, serverless (details)

... --host laptop / --host local

list/search/pick filter: only that machine's history

backscroll stats

how much is stored

backscroll prune --older 30d

forget old entries

backscroll delete <id>

forget one entry (that curl -H "Authorization: ...")

backscroll redact <id|-N>

permanently mask tokens/keys/passwords in a stored entry (--dry-run previews)

backscroll mcp

MCP server: let your AI coding agent query your history (details)

backscroll off / on

pause / resume recording in this session

backscroll doctor

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 run rc snippet above (or set tmux's default-command "backscroll run"). Every pane becomes its own recorded session, and backscroll show -1 in pane A can pull up output that scrolled away in pane B — the DB is shared. The $BACKSCROLL_ACTIVE guard prevents double-recording if you nest.

  • Outside tmux (backscroll run then tmux inside) 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.conf binds prefix + B to 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, q back 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 machine

No 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

script / asciinema

✗ (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 prune compacts 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/ignore and matching commands are never stored:

    ^vault
    ^op\b
    password|token|secret
  • backscroll off pauses recording for the session (backscroll on resumes) — 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 --redact and export --redact do 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 30d keeps 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.key if 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

Install Server
A
license - permissive license
A
quality
A
maintenance

Maintenance

Maintainers
1hResponse time
0dRelease cycle
19Releases (12mo)
Commit activity

Latest Blog Posts

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