How Nodeterm Resumes AI Agent Sessions After a Cold Start: 3-Step Recovery

Nodeterm resumes AI agent sessions after a cold start by persisting session identifiers to disk, capturing periodic scrollback snapshots of tmux panes, and replaying stored output before issuing agent-specific resume commands when the renderer detects a fresh PTY creation.

Nodeterm orchestrates AI agents like Claude, Codex, and Gemini inside persistent tmux sessions, but machine reboots destroy these sockets and create cold start scenarios. The application implements a robust recovery mechanism that restores the exact terminal state and conversation context without user intervention. This article examines the three-step persistence strategy implemented in the eneskirca/nodeterm repository that enables seamless session continuity.

Step 1: Persist the Session Identifier

When an agent first launches, the hook server reports a unique sessionId to the core. This identifier is immediately stored on the node’s data.sessionId field and serialized to the project file at .nodeterm/project.json.

The sessionId extraction happens in src/shared/agents/normalize.ts, which processes hook events and normalizes agent status updates. The workspace serializer in src/renderer/state/workspace.ts then persists this value via the nodeStatesToFlow and flowToNodeStates functions, ensuring the ID survives application quits, git operations, and machine restarts.

Because the session identifier is written to a git-shareable project file, nodeterm can locate the previous conversation context even after a complete system reboot.

Step 2: Capture Periodic Scrollback Snapshots

While the tmux session remains active, the PTY manager periodically snapshots the pane’s output into a byte-capped log file. The src/core/scrollback-store.ts module handles tmux capture-pane -e execution, storing raw terminal output (capped at approximately 256 KB) to <userData>/terminal-scrollback/<sessionId>.log.

The capture timer is controlled by SCROLLBACK_SNAPSHOT_MS in src/core/pty-manager.ts, which also triggers an immediate snapshot on PTY detach. This ensures the disk record reflects the latest terminal state before any potential cold start event.

// src/core/scrollback-store.ts – snapshotting scrollback
export async function captureScrollback(sessionId: string) {
  const out = await execTmux(['capture-pane', '-e', '-t', `nt-${sessionId}`]);
  await writeFileAtomic(
    path.join(userDataDir, 'terminal-scrollback', `${sessionId}.log`),
    out.stdout
  );
}

Step 3: Detect Cold Starts and Replay State

When the renderer mounts a TerminalNode, it checks the PtyCreateResult.fresh flag returned by src/core/pty-manager.ts. If fresh is true, the tmux session does not exist, triggering the cold-restore workflow in src/renderer/nodes/TerminalNode.tsx.

The recovery process executes two coordinated actions:

  1. Scrollback Replay: The renderer calls pty.readScrollback(sessionId) to load the saved log, writes a separator line (--- session restored ---), and injects the historical output into the xterm instance.
  2. Resume Command Injection: Using the stored sessionId, the system builds an agent-specific resume command and transmits it to the new PTY.
// src/renderer/nodes/TerminalNode.tsx – cold‑restore handling
if (ptyCreateResult.fresh) {
  // 1️⃣ Replay saved scrollback
  const sb = await pty.readScrollback(nodeId);   // reads <userData>/terminal‑scrollback/…
  xterm.write(`\r\n--- session restored ---\r\n`);
  xterm.write(sb);

  // 2️⃣ Send the resume command (sessionId is stored on the node)
  const cmd = resumeCommand('claude', node.data.sessionId);
  transport.write(node.id, cmd);
}

Assembling Agent-Specific Resume Commands

The src/shared/agents/launch.ts module contains the resumeCommand and resumeCommandWith functions that construct CLI strings according to each agent’s specific grammar. The mapping of resume flags is defined in src/shared/agents/config.ts:

  • Claude: --resume <id>
  • Codex: resume <id>
  • Gemini: --resume <id>
  • Opencode: --session <id>
  • Copilot: --resume=<id>

These functions sanitize the session ID to prevent injection attacks before assembling the final command string.

// src/shared/agents/launch.ts – building a resume command
import { resumeCommand } from './config';

// `inputs.sessionId` holds the persisted session identifier.
const resumeBase = inputs.sessionId
  ? resumeCommandWith(baseCmd, capId, inputs.sessionId)   // e.g. "claude --resume abc-123"
  : null;
const launchCmd = resumeBase ?? baseCmd;                  // fresh launch if no ID

// The command is finally written to the PTY when the terminal mounts.
transport.write(nodeId, launchCmd);

Once the resume command executes, the agent CLI loads its previous transcript from its own storage and continues the conversation exactly where it left off.

Summary

Frequently Asked Questions

How does nodeterm know if a tmux session is lost?

The src/core/pty-manager.ts module checks for tmux session existence when creating a PTY. It returns a fresh boolean in PtyCreateResult indicating whether the session was newly created (true) or already existed (false). The TerminalNode.tsx component uses this flag to trigger cold-restore logic, replay scrollback, and issue resume commands only when necessary.

Where is the session scrollback stored between restarts?

Scrollback data is stored in the user data directory under terminal-scrollback/<sessionId>.log. The src/core/scrollback-store.ts module manages these files, capturing output via tmux capture-pane -e on a timer (SCROLLBACK_SNAPSHOT_MS) and immediately upon PTY detach. Each agent session maintains its own isolated log file keyed by the persistent sessionId.

What happens if the sessionId is missing from the project file?

If inputs.sessionId is undefined in src/shared/agents/launch.ts, the resumeCommandWith function returns null and the system falls back to a standard launch command without resume flags. The agent starts a fresh conversation rather than attempting to restore a previous session, preventing errors from invalid or stale session identifiers.

Which AI agents support the resume functionality?

According to the configuration in src/shared/agents/config.ts, the resume mechanism supports Claude, Codex, Gemini, Grok, Opencode, and Copilot. Each agent uses a specific CLI grammar (e.g., --resume, resume, --session, or --resume=<id>) that the resumeCommand function injects with the stored sessionId to reconnect to the previous conversation state.

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 →