How Nodeterm Replays Scrollback Data on Cold Start: Terminal Session Persistence Explained
Nodeterm replays scrollback data on cold start by detecting when a tmux session no longer exists, then retrieving a pre-saved snapshot from disk via the pty.readScrollback IPC call and writing it directly to the xterm instance before recreating the underlying session.
Nodeterm is an open-source terminal management application that maintains persistent terminal sessions across machine reboots. When the application launches after a cold start and discovers the underlying tmux session has been destroyed, it seamlessly reconstructs the previous terminal output by replaying stored scrollback data. This article examines the implementation details of how nodeterm captures, stores, and restores terminal scrollback to ensure users never lose their session context.
Understanding the Cold Start Problem in Terminal Multiplexers
Why Tmux Sessions Are Lost on Reboot
Nodeterm relies on tmux to manage persistent terminal sessions, but tmux itself is an in-memory process. When a machine reboots or the tmux server is terminated, all active panes and their scrollback buffers are destroyed. Without a dedicated persistence layer, users would return to empty terminals after restarting their computer, losing critical command history and output context.
The Warm Attach vs. Fresh Start Distinction
In src/main/pty-manager.ts, the PtyManager class distinguishes between two initialization scenarios:
- Warm attach: A tmux session with the expected name (
nt-${sessionId}) already exists. The terminal connects to the live session, which immediately redraws its current state. No scrollback replay is necessary because tmux preserves the active buffer. - Cold start (Fresh): The
tmux has-sessioncheck fails, indicating the session was destroyed. Thefreshflag is set totrue, triggering the scrollback restoration workflow before a new tmux session is created.
Capturing Scrollback Snapshots Before Shutdown
To ensure data availability after a reboot, nodeterm periodically persists terminal output to disk. The captureScrollback function in src/core/scrollback-store.ts handles this by executing tmux commands directly against the active pane.
The snapshot generation process follows these steps:
- Capture raw output: Execute
tmux capture-pane -e -t nt-${sessionId}to grab the current pane content, including escape sequences for color and formatting. - Apply byte limits: Truncate the output to
SCROLLBACK_MAX_BYTES(256 KB by default) to prevent excessive disk usage. - Atomic write: Store the data in the user data directory under
terminal-scrollback/${sessionId}.logusing atomic file writes to prevent corruption during unexpected shutdowns.
// src/core/scrollback-store.ts
export async function captureScrollback(sessionId: string) {
// Capture current pane content via tmux, preserving escape sequences
const raw = await execTmux(['capture-pane', '-t', `nt-${sessionId}`, '-e']);
// Truncate to configured limit (256 KB default)
const truncated = raw.slice(0, SCROLLBACK_MAX_BYTES);
// Write to persistent storage
await writeFileAtomic(
path.join(scrollbackDir, `${sessionId}.log`),
truncated
);
}
Detecting Cold Starts and Reading Stored Data
When the renderer process detects a cold start, it requests the stored scrollback via the pty:readScrollback IPC channel defined in src/shared/ipc.ts. The main process handles this request in src/core/scrollback-store.ts by reading the corresponding file from the terminal-scrollback directory and returning the raw buffer to the renderer.
// src/core/scrollback-store.ts (IPC handler)
ipcMain.handle('pty:readScrollback', async (_, sessionId: string) => {
const filePath = path.join(scrollbackDir, `${sessionId}.log`);
return readFile(filePath, 'utf8');
});
This separation of concerns ensures that file system access remains in the main process while the renderer handles UI updates, adhering to Electron's security model.
Replaying Scrollback into the Terminal UI
The restoration logic resides in src/renderer/nodes/TerminalNode.tsx, the React component responsible for mounting the xterm.js instance. When the component initializes with fresh = true, it executes the replay sequence before attaching to the new tmux session.
The component performs the following actions:
- Calls
window.nodeTerminal.pty.readScrollback(sessionId)to fetch the historical output. - Writes the retrieved data to the xterm instance using
term.write(scrollback), which parses the ANSI escape sequences to reconstruct colors and formatting. - Appends a visual separator (
--- session restored ---) to indicate the boundary between historical replay and new output. - Proceeds to create the new tmux session and attach the pty.
// src/renderer/nodes/TerminalNode.tsx (simplified logic)
useEffect(() => {
if (fresh) {
// Fetch the stored scrollback from main process
const scrollback = await window.nodeTerminal.pty.readScrollback(sessionId);
// Replay into xterm instance
term.write(scrollback);
term.writeln('\r\n--- session restored ---\r\n');
}
// Create or attach to tmux session after replay
await initializePtySession(sessionId);
}, [sessionId, fresh]);
Handling Agent Session Continuation
For AI agent sessions (Claude, Codex, Gemini), the scrollback replay provides the visual context, but the agent process itself must also resume. After writing the scrollback to xterm, TerminalNode.tsx checks if the node represents a resumable agent. If so, it launches the agent CLI with the --resume ${sessionId} flag, ensuring the agent's internal state synchronizes with the displayed terminal history.
// Agent resume logic following scrollback replay
if (isAgent && fresh) {
const resumeCommand = `claude --resume ${sessionId}`;
await window.nodeTerminal.pty.write(sessionId, resumeCommand);
}
Summary
- Scrollback persistence: Nodeterm captures terminal output using
tmux capture-pane -eand stores it insrc/core/scrollback-store.ts, capped at 256 KB per session. - Cold start detection: The
PtyManagerinsrc/main/pty-manager.tschecks for existing tmux sessions; absence indicates a cold start requiring scrollback restoration. - IPC retrieval: The renderer requests historical data via the
pty:readScrollbackchannel, while the main process reads from theterminal-scrollback/directory. - UI reconstruction:
TerminalNode.tsxwrites the retrieved scrollback directly to the xterm instance before creating a new tmux session, ensuring visual continuity. - Agent synchronization: Resumable agents are restarted with session-specific flags after the scrollback replay completes, maintaining workflow continuity.
Frequently Asked Questions
Where does nodeterm store scrollback snapshots?
Nodeterm stores scrollback snapshots in the user's application data directory under a terminal-scrollback/ subdirectory. Each session receives its own file named ${sessionId}.log, written atomically by the captureScrollback function in src/core/scrollback-store.ts to prevent data corruption during crashes.
What is the maximum size of a scrollback snapshot?
By default, nodeterm caps scrollback snapshots at 256 KB per session, configurable via the SCROLLBACK_MAX_BYTES constant. The captureScrollback function truncates the raw tmux output to this limit before writing to disk, balancing historical fidelity with storage efficiency.
How does nodeterm distinguish between a cold start and a warm attach?
In src/main/pty-manager.ts, the application executes tmux has-session -t nt-${sessionId} before initializing the terminal. If the command returns successfully, the session is alive and the connection proceeds as a warm attach. If the check fails, the fresh flag is set to true, triggering the scrollback replay workflow for a cold start.
Does scrollback replay work for AI agent sessions?
Yes. When a terminal node hosts a resumable agent (Claude, Codex, or Gemini), the scrollback replay restores the visual history first. Immediately after, TerminalNode.tsx launches the agent CLI with the --resume ${sessionId} flag, allowing the agent to synchronize its internal state with the displayed terminal history and continue the conversation seamlessly.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →