# How Nodeterm Replays Scrollback Data on Cold Start: Terminal Session Persistence Explained

> Learn how Nodeterm replays scrollback data on cold start. Discover the process of saving and restoring terminal session history for seamless restarts.

- Repository: [eneskirca/nodeterm](https://github.com/eneskirca/nodeterm)
- Tags: internals
- Published: 2026-08-25

---

**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`](https://github.com/eneskirca/nodeterm/blob/main/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-session` check fails, indicating the session was destroyed. The `fresh` flag is set to `true`, 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`](https://github.com/eneskirca/nodeterm/blob/main/src/core/scrollback-store.ts) handles this by executing tmux commands directly against the active pane.

The snapshot generation process follows these steps:

1. **Capture raw output**: Execute `tmux capture-pane -e -t nt-${sessionId}` to grab the current pane content, including escape sequences for color and formatting.
2. **Apply byte limits**: Truncate the output to `SCROLLBACK_MAX_BYTES` (256 KB by default) to prevent excessive disk usage.
3. **Atomic write**: Store the data in the user data directory under `terminal-scrollback/${sessionId}.log` using atomic file writes to prevent corruption during unexpected shutdowns.

```typescript
// 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`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/ipc.ts). The main process handles this request in [`src/core/scrollback-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/scrollback-store.ts) by reading the corresponding file from the `terminal-scrollback` directory and returning the raw buffer to the renderer.

```typescript
// 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`](https://github.com/eneskirca/nodeterm/blob/main/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:

1. Calls `window.nodeTerminal.pty.readScrollback(sessionId)` to fetch the historical output.
2. Writes the retrieved data to the xterm instance using `term.write(scrollback)`, which parses the ANSI escape sequences to reconstruct colors and formatting.
3. Appends a visual separator (`--- session restored ---`) to indicate the boundary between historical replay and new output.
4. Proceeds to create the new tmux session and attach the pty.

```tsx
// 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`](https://github.com/eneskirca/nodeterm/blob/main/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.

```tsx
// 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 -e` and stores it in [`src/core/scrollback-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/scrollback-store.ts), capped at 256 KB per session.
- **Cold start detection**: The `PtyManager` in [`src/main/pty-manager.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/pty-manager.ts) checks for existing tmux sessions; absence indicates a cold start requiring scrollback restoration.
- **IPC retrieval**: The renderer requests historical data via the `pty:readScrollback` channel, while the main process reads from the `terminal-scrollback/` directory.
- **UI reconstruction**: [`TerminalNode.tsx`](https://github.com/eneskirca/nodeterm/blob/main/TerminalNode.tsx) writes 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`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/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.