Scrollback dump — every shell pane across every tab, to disk
tmnl has two scrollback-export verbs.
The first is scrollback.save — the focused pane only. You’re working
in one tab, something just scrolled off the top, you want the buffer on
disk. One file, one pane, done.
The second — the subject of this page — is scrollback.dump_all_panes.
It walks every tab + every shell pane in the focused window and writes one
file per pane into a fresh timestamped directory, plus an INDEX.txt
mapping each tab/pane to its filename. The use case is different: “I’m
about to quit and want everything captured,” or “I’m filing a bug that
spans tabs,” or “this multi-hour build session has scrollback in five
panes and I want all of it.”
Different intent, different file shape. The two commands are deliberately separate primitives — saving 30 files when you wanted 1 is noise; saving 1 file when you wanted 30 is data loss.
Running it
Section titled “Running it”From the command palette (Cmd+Shift+P or F1), search for
“Dump all shell scrollback”. Press Enter. The command id is
scrollback.dump_all_panes; there’s no default chord. Bind one in
~/.config/tmnl/config.toml if you reach for it often:
[[keybind]]key = "cmd+shift+s"command = "scrollback.dump_all_panes"The command is gated on no_modal_open — it stays inert while the
command palette, Settings overlay, or any other modal is in the foreground,
so the chord can’t fire while you’re searching for it.
What gets written
Section titled “What gets written”On macOS, the dump dir is created under ~/Desktop and revealed in
Finder when the write completes:
~/Desktop/tmnl-scrollback-dump-1718295496/├── INDEX.txt├── tab00-pane00-zsh.txt├── tab00-pane01-claude-code.txt├── tab01-pane00-pnpm-build.txt├── tab02-pane00-TE-1234.txt└── tab03-pane00-mixr.txtThe trailing number on the dir is the Unix timestamp at dump time, so two
dumps in the same session don’t collide. If ~/Desktop isn’t a directory
(headless macOS, or another OS), the dir falls back to $HOME; if that’s
also unavailable, the cwd.
Per-file naming
Section titled “Per-file naming”tab<NN>-pane<NN>-<sluggified-tab-label>.txttab<NN>andpane<NN>are zero-padded two-digit indices into the window’s tab list and the tab’s pane vec, in render order. They mean you can sort the directory listing and read top-to-bottom matching the window layout.- The label slug is the tab’s chip label, with anything outside
[A-Za-z0-9_-]collapsed to-, capped at 40 chars. Emoji, Unicode, spaces, slashes — all get normalised so the filename is portable and readable acrossls, Finder, and the usual shell tooling. - An empty tab label falls back to
tab-<idx>.
INDEX.txt
Section titled “INDEX.txt”The index file at the top of the dir has one line per dumped pane plus a summary footer:
tmnl scrollback dump @ 1718295496
tab 0 pane 0 (zsh): 32 rows + 410 scrollback → tab00-pane00-zsh.txttab 0 pane 1 (claude code): 32 rows + 198 scrollback → tab00-pane01-claude-code.txttab 1 pane 0 (pnpm build): 32 rows + 5021 scrollback → tab01-pane00-pnpm-build.txttab 2 pane 0 (TE-1234): 48 rows + 12 scrollback → tab02-pane00-TE-1234.txttab 3 pane 0 (mixr): 32 rows + 0 scrollback → tab03-pane00-mixr.txt
wrote 5/5 shell panes total.Each line carries:
- The tab index and pane index — match these against the tab strip / pane layout to identify a row.
- The live tab label (un-slugified — the readable form).
<rows> rows + <scrollback_len> scrollback—rowsis the visible grid height at dump time;scrollback_lenis how many additional rows sit above the visible region in the vt100 scrollback buffer.- The on-disk filename.
The summary footer reports wrote <N>/<M> shell panes total, where M
is the count of shell panes the dump pass found and N is the count
that successfully serialised. A mismatch is rare but indicates an IO
failure on one or more panes — the per-line error makes it explicit:
tab 1 pane 0: ERROR No space left on device (os error 28)Per-pane file format
Section titled “Per-pane file format”Each pane file is the pane’s full text content — scrollback above the
visible region, plus the visible grid — with one row per line and
trailing whitespace trimmed. No ANSI escape codes. No frame markers. No
header. It’s the same shape scrollback.save produces, just one per
pane.
The content is captured from the vt100 parser via all_rows_text(),
which walks the scrollback ring buffer + the live screen in render
order. The vt100 grid is cols × rows of styled cells; the dump
flattens style information away — colours, attrs, cursor position are
not preserved. The use case is “I want the text” not “I want a
session replay.”
What gets included — and what doesn’t
Section titled “What gets included — and what doesn’t”The dump pass walks app.win.tabs (the focused window only) and visits
every pane. It includes:
- Shell panes with an attached session (one with a live pty). Each becomes a file.
It skips:
- Native panes (mnml, mixr, custom
tmnl-protocolclients). They don’t have a vt100 grid — their content is structuredFrames of cells, not a parseable text buffer. The dump pass walks past them silently. - Browser panes. Same shape — no vt100 grid.
- Shell panes whose session is detached (an
adoptedpty that hasn’t connected yet, or a session markedreleasedafter a pty-fd handoff — see Native tabs).
A window with three native tabs and one shell tab will produce a dir
with one .txt file in it. The INDEX.txt summary footer counts only
shell panes, so the <N>/<M> ratio always refers to dump-eligible
panes.
The dump is focused-window-only. A multi-window session with three windows running dumps the focused window’s panes; background windows need a separate dump pass after you `Cmd+“ cycle to them. (Adding a “dump all windows” verb is a possible follow-up; today’s primitive is narrower so the resulting directory doesn’t explode.)
When to reach for it
Section titled “When to reach for it”Three flows it fits.
Quit-time capture. You’ve been working all day across six tabs.
Build logs in tab 1, a Claude Code session in tab 2, a few shells in
the rest. Before you Cmd+Q, you want the lot. One palette command,
the dir opens in Finder, you have a complete snapshot.
Bug filing across panes. A reproduction involves what pnpm build
said in one tab and what psql said in another, plus the shell error
that triggered them both. Dump-all, attach the timestamped dir to the
issue (tar czf dump.tgz ~/Desktop/tmnl-scrollback-dump-* is the usual
follow-up), include INDEX.txt as the orientation.
Long-running session triage. A multi-hour build that scrolled off
the visible region in three panes, plus a shell that’s been running
your test loop, plus mnml in a native tab. Dump-all writes the shell
buffers and skips the native; you read INDEX.txt to see which pane
had the most scrollback (<scrollback_len> in the index line is
the answer), open that file first, search.
Implementation notes
Section titled “Implementation notes”The whole verb is ~100 lines in
src/command.rs
under the scrollback.dump_all_panes builtin. The text extraction
goes through ShellSession::all_rows_text() in
src/shell/scroll.rs
— the same method scrollback.save calls, so the per-pane file format
matches.
The slug helper at the top of command.rs is shared with the
single-pane scrollback.save path (and the selection-export path) so
filenames stay consistent across all three verbs.
Failures are best-effort:
- The dir mkdir failure logs + bails (
log::warn!("scrollback dump mkdir …")). - A single per-pane write failure logs + records the error inline in INDEX.txt, then the pass continues to the next pane. You get a partial dump rather than nothing.
- The
open <dir>Finder-reveal on macOS is silent on failure — the files are still on disk; the log line records the path either way.
In-flight crash-recovery dump (automatic, every 30 seconds)
Section titled “In-flight crash-recovery dump (automatic, every 30 seconds)”The two verbs above are explicit — you ask, tmnl writes. There’s a third path that runs by itself, with no command: an in-flight dump that survives a crash.
When cfg.scrollback_persist_on_quit is on (the same knob that
controls the clean-exit save) tmnl walks every Shell pane in the
focused window every ~30 seconds and writes its current scrollback
to a per-tab file:
~/.cache/tmnl/scrollback/├── tab-0.txt├── tab-1.txt└── tab-2.txtOne file per tab, indexed by tab position. Overwritten on every
dump — this is “the last 30 seconds is recoverable” insurance, not
a backup. A clean quit still writes scrollback-last.txt via the
historical path, so you have two artifacts: the focused tab’s
end-of-session capture and a per-tab snapshot from up to 30s
before the crash.
The dump runs in App::tick, throttled by an Instant
comparison — cheap when not due, silent on FS errors so a broken
cache dir can’t gum up the loop. There’s no menu item or command
for it; the cfg knob alone gates it on/off.
When to look at the in-flight files:
- A panic, SIGKILL, or sudden window close lost the session. Check
~/.cache/tmnl/scrollback/tab-<N>.txtto recover what was on screen. - You want to grep across tabs without invoking the explicit
dump_all_panescommand — the files are already there.
When NOT to rely on them:
- They’re focused-window-only — background windows aren’t dumped. Multi-window sessions need an explicit dump if you want the others.
- The 30-second cadence means you can lose up to ~30s of recent output between dumps.
- Native panes (mnml, mixr) are skipped — same reason
dump_all_panesskips them.
Disabling it: flip scrollback_persist_on_quit = false in
~/.config/tmnl/config.toml. That’s the same switch that disables
the clean-exit save — one knob, both behaviors.
Automatic restore on next launch
Section titled “Automatic restore on next launch”When tmnl boots and finds saved tabs via the existing session
restore (~/.local/state/tmnl/session.json), it now also checks
~/.cache/tmnl/scrollback/tab-<idx>.txt for each Shell tab. If a
file exists, the contents are fed back into the new pty’s vt100
parser before any fresh output appears — so you open tmnl after a
crash and see your old terminal output above the new prompt.
The pty itself is fresh: the running process is gone (a crash killed it), and only the visible history is reproduced. Hitting ↵ on the restored prompt runs in the new session, not the dead one — the scrollback is read-only history.
Two practical notes:
- The replay normalizes
\nto\r\nso the parser advances the cursor properly between rows. A trailing newline is appended so the new prompt lands cleanly below the replay. - The dump file is left in place for the next dump tick to overwrite. Reopening tmnl within the same dump cycle replays the same snapshot.
Where to go next
Section titled “Where to go next”- Tabs, splits, and panes — the tab + pane
model the dump walks. Useful for matching
tab<NN>-pane<NN>indices back to a window layout. - Multi-window — dump-all is focused-window-only; a multi-window session needs a pass per window.
- Native tabs — why native panes are silently skipped (they don’t have a vt100 grid to flatten).
- Updates — the other “background utility” page.
- FEATURES.md — the shipped-feature inventory.