PtyCreateResult.fresh in Nodeterm: The Key to Cold Restore and Scrollback Replay

PtyCreateResult.fresh is a boolean flag that indicates whether a tmux session was newly created (true) or already existed (false), determining whether Nodeterm must replay saved scrollback history to restore terminal state during a cold restore.

Nodeterm is an open-source terminal emulator that maintains persistent sessions through tmux. When the application initializes a PTY (pseudo-terminal) session via createPty(), it returns a PtyCreateResult object defined in src/shared/types.ts (lines 148-170). The fresh property within this result serves as the critical decision point for whether the terminal performs a cold restore with scrollback replay or a warm attach to an existing session.

Understanding the Fresh Flag in PtyCreateResult

In the type definitions located at src/shared/types.ts, the PtyCreateResult interface includes the fresh: boolean field. This property communicates the state of the tmux session backend to the renderer process.

  • When fresh is true: The tmux server did not have an existing session with the requested ID. Nodeterm created a new tmux session from scratch, meaning the terminal screen starts blank unless historical output is restored from disk.
  • When fresh is false: The session already existed in the tmux server. This occurs when another client is connected, or the session survived a previous disconnect.

This distinction directly impacts how the terminal UI initializes the xterm instance and whether it loads historical scrollback data from <userData>/terminal-scrollback/.

Cold Restore vs. Warm Attach Behavior

The renderer uses the fresh flag to choose between two distinct initialization paths. The behavior differs significantly depending on whether the session is truly new or being reused.

Warm Attach (fresh: false)

When fresh is false, the terminal connects to an already-running tmux session. In this scenario:

  • The tmux server retains the full scrollback history and active screen buffer.
  • The renderer simply attaches the xterm to the live PTY stream via attachPtyStream().
  • No scrollback replay occurs because tmux itself redraws the screen with existing content when the client connects.

This path applies to joiner clients (co-attached sessions) or reconnections where the tmux server never terminated. A joiner client always receives fresh: false and therefore does not redo the replay.

Cold Restore (fresh: true)

When fresh is true, indicating a cold start (tmux server died or first-time launch):

  • The application must re-populate the terminal's scrollback to provide seamless continuity.
  • Nodeterm loads a previously saved snapshot from <userData>/terminal-scrollback/<sessionId>.snapshot via src/core/scrollback-store.ts.
  • The renderer injects this historical output into the freshly created xterm before any live output arrives.
  • This gives users the appearance of an uninterrupted session despite the tmux process being recreated.

The original client that experiences a cold start receives fresh: true and triggers this restoration logic, while joiners skip it.

Implementation Across the Codebase

The fresh flag flows through three critical components that coordinate the restore process:

  • src/core/pty-manager.ts: Contains the core logic that spawns PTY sessions, checks for existing tmux sessions, and sets the fresh boolean accordingly in the returned PtyCreateResult.
  • src/core/scrollback-store.ts: Persists terminal output to disk during normal operation and retrieves snapshots during cold restore when requested by the renderer.
  • src/renderer/nodes/TerminalNode.tsx: Consumes the PtyCreateResult and implements the conditional logic to either replay scrollback (when fresh is true) or attach directly to the live stream (when fresh is false).

Practical Implementation Example

The following pattern demonstrates how to handle the fresh flag when initializing a terminal in Nodeterm:

import { createPty } from '@shared/pty-manager';
import { PtyCreateResult } from '@shared/types';
import fs from 'fs/promises';
import path from 'path';

async function launchTerminal(sessionId: string, userDataDir: string) {
  const result: PtyCreateResult = await createPty({ sessionId });

  if (result.fresh) {
    // Cold restore: replay saved scrollback into the new xterm
    const scrollbackPath = path.join(
      userDataDir, 
      'terminal-scrollback', 
      `${sessionId}.snapshot`
    );
    const scrollback = await fs.readFile(scallbackPath, 'utf8');
    xterm.write(scrollback);
  } else {
    // Warm attach: tmux redraws the screen; attach to live stream only
    attachPtyStream(result.sessionId);
  }
}

In this implementation, the fresh check prevents unnecessary disk I/O during warm attaches while ensuring historical context is available during cold starts. The scrollback is injected via xterm.write() before the live PTY stream begins pumping data.

Summary

  • PtyCreateResult.fresh signals whether a tmux session was created anew (true) or reused (false) in src/shared/types.ts.
  • Cold restore (fresh: true) requires loading scrollback snapshots from <userData>/terminal-scrollback/ via src/core/scrollback-store.ts to restore terminal history.
  • Warm attach (fresh: false) skips scrollback replay because the existing tmux session maintains the screen buffer and handles redraws automatically.
  • Joiner clients always receive fresh: false, while the original client experiencing a cold start receives fresh: true and restores the terminal history.

Frequently Asked Questions

What triggers a cold restore versus a warm attach?

A cold restore occurs when the tmux server has died, been killed, or is starting for the first time, forcing Nodeterm to create a fresh session (fresh: true). A warm attach happens when connecting to an existing tmux session that survived the disconnect or when a second client joins the same session (fresh: false).

Where does Nodeterm store scrollback data for cold restores?

Scrollback snapshots are stored in the user's data directory under terminal-scrollback/ as individual snapshot files (e.g., <sessionId>.snapshot), managed by src/core/scrollback-store.ts. These files are read during cold restore and written continuously during normal operation.

Why does the renderer skip scrollback replay during warm attach?

During a warm attach, the tmux server still holds the complete scrollback history and screen state. When the renderer connects, tmux automatically redraws the entire screen content, making manual scrollback injection redundant and potentially conflicting with the live session state.

How does PtyCreateResult.fresh affect performance?

When fresh is true, the renderer performs file system I/O to load scrollback history, introducing a brief initialization delay. When fresh is false, the terminal connects immediately to the live stream without disk access, resulting in faster attachment times and lower resource usage.

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 →