How nodeterm Handles Scrollback Snapshots for Cold Restores

nodeterm preserves terminal history across cold restarts by writing a byte-capped snapshot of the tmux pane to disk on detach, then replaying that snapshot into a fresh xterm instance when the node remounts.

The eneskirca/nodeterm project is an open-source terminal node manager that sits on top of tmux. When a terminal closes — whether the application quits, the machine reboots, or the process is killed — the underlying tmux session disappears, taking the scrollback with it. To make cold starts feel seamless, nodeterm implements a scrollback snapshot system that persists terminal output to disk and replays it on next mount.

How the Scrollback Snapshot Flow Works

The cold-restore process follows a five-step lifecycle that spans both the core process and the renderer. Here is the complete flow as implemented in the nodeterm source.

1. Capture on Detach

When a terminal node is detached — the app closes, the node goes off-screen, or the process is killed — PtyManager calls writeScrollback() from src/core/scrollback-store.ts. This function writes a byte-capped dump of the pane's visible screen plus its tmux scrollback to disk.

The snapshot file lives under <userData>/terminal-scrollback/<sessionId>.txt. The cap is approximately 256 KB, which prevents huge files from being written and keeps the restore operation fast.

2. Persist the Snapshot

The snapshot remains on disk even if the tmux server is gone. This is the key to cold-restore: the file survives a full process death or system reboot. Because it lives in userData (not in the tmux server), it is immune to tmux session teardown.

3. Cold-Restore Detection

When a new TerminalNode mounts, the renderer asks the core via pty.readScrollback whether a snapshot exists for the node's persistent session ID. The node's fresh flag determines the restore type:

  • If fresh === true (the tmux session did not exist on app start), the snapshot is treated as a cold-restore.
  • If fresh === false (the session is still alive), no replay is needed.

4. Replay with a Separator

The renderer writes the stored snapshot back into the newly created xterm instance. A special "session restored" separator line is inserted before the historical output, so the user can visually distinguish where the old output ends and new output begins.

After the replay, the terminal accepts new data from the freshly spawned tmux pane without interruption.

5. Agent-Specific Handling

Agent nodes — Claude, Codex, Gemini, and others — follow the same snapshot replay path but also resume their internal state. The CLI is relaunched with a --resume <sessionId> argument, so the agent's conversation context and tool state continue where they left off.

Core Components Behind Scrollback Snapshots

The scrollback snapshot architecture spans four modules. Each plays a specific role in the capture, persistence, and replay pipeline.

Component Responsibility
src/core/scrollback-store.ts Provides writeScrollback, readScrollback, and deleteScrollback. Handles the on-disk format and the 256 KB size limit.
src/core/pty-manager.ts Hooks into terminal lifecycle events. On detach it triggers writeScrollback; on create it reads the snapshot via readScrollback.
src/renderer/nodes/TerminalNode.tsx Determines whether the node is a cold-restore (fresh === true). Fetches the snapshot and injects it into the xterm instance, adding the "session restored" separator.
docs/CLAUDE.md Documents the design rationale and the size-capping policy.

In src/core/scrollback-store.ts, the file API is intentionally small — three functions that handle write, read, and delete. This keeps the on-disk format simple and makes the restore path trivial to reason about. src/core/pty-manager.ts wires these functions into the terminal lifecycle, and src/renderer/nodes/TerminalNode.tsx makes the UI decision about whether to replay.

Code Examples

Saving a Snapshot When a Pane Detaches

// core/pty-manager.ts (simplified)
import { writeScrollback } from './scrollback-store';

async function handleDetach(sessionId: string, pty: Pty) {
  // Capture the visible screen + tmux scrollback (capped at 256 KB)
  const snapshot = await pty.capture({ full: true });
  await writeScrollback(sessionId, snapshot);
}

Reading the Snapshot for a Cold-Restore

// renderer/nodes/TerminalNode.tsx (simplified)
import { readScrollback } from '../../core/scrollback-store';

async function maybeReplayScrollback(nodeId: string) {
  const scrollback = await readScrollback(nodeId);
  if (scrollback) {
    // Insert the separator so the user knows where the replay ends
    term.write(`${SCROLLBACK_SEPARATOR}\r\n`);
    term.write(scrollback);
  }
}

Launching an Agent with a Resumed Session

// core/agent-session-name.ts (simplified)
export function launchAgent(sessionId: string, agentId: string) {
  const resumeCmd = `${agentId} --resume ${sessionId}`;
  pty.sendText(resumeCmd);
}

Key Source Files

File Role
src/core/scrollback-store.ts Core API for persisting and reading scrollback snapshots
src/core/pty-manager.ts Integrates the scrollback store with terminal lifecycle events
src/renderer/nodes/TerminalNode.tsx UI side that decides whether to replay a snapshot on mount
docs/CLAUDE.md (Scrollback replay section) Design overview and rationale for the 256 KB cap

Summary

  • nodeterm captures terminal scrollback on detach via writeScrollback() and stores it under <userData>/terminal-scrollback/ as a byte-capped (~256 KB) text file.
  • A cold restore is detected when a node mounts with the fresh flag set to true, meaning the tmux session did not exist at startup.
  • The renderer replays the snapshot into the new xterm instance with a "session restored" separator line to distinguish old output from new.
  • Agent nodes resume their internal state by relaunching the CLI with a --resume <sessionId> argument.
  • The 256 KB cap keeps snapshots small and restore operations fast while preserving the full visible output of the previous session.

Frequently Asked Questions

What triggers a scrollback snapshot in nodeterm?

A snapshot is captured whenever a terminal node detaches. The PtyManager hooks into terminal lifecycle events and calls writeScrollback() from src/core/scrollback-store.ts when the pane goes away. The snapshot captures the visible screen plus the tmux scrollback, capped at 256 KB.

How does nodeterm know a cold restore is needed?

When a TerminalNode mounts, the renderer checks the node's fresh flag. If the flag is true, it means the tmux session did not exist at app start, so the previous session was lost. The renderer then reads the stored snapshot via readScrollback and replays it into the xterm instance.

What is the "session restored" separator?

It is a special line that TerminalNode.tsx inserts into the terminal before replaying the snapshot. It visually marks the boundary between historical output (restored from disk) and new output from the freshly spawned tmux pane, so users know where the old session ends.

What happens to agent sessions during a cold restore?

Both the scrollback and the agent's internal state are restored. The CLI is relaunched with a --resume <sessionId> argument, which instructs the agent (Claude, Codex, Gemini, etc.) to continue from its previous context — not just the visible terminal output.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →