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

> Discover how Nodeterm resumes AI agent sessions after a cold start. Learn the 3-step recovery process involving disk persistence, scrollback snapshots, and output replay.

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

---

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

The `sessionId` extraction happens in [`src/shared/agents/normalize.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/normalize.ts), which processes hook events and normalizes agent status updates. The workspace serializer in [`src/renderer/state/workspace.ts`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/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.

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

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

```typescript
// 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

- **Session identifiers** are extracted from hook events in [`src/shared/agents/normalize.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/normalize.ts) and persisted to [`.nodeterm/project.json`](https://github.com/eneskirca/nodeterm/blob/main/.nodeterm/project.json) via [`src/renderer/state/workspace.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/state/workspace.ts).
- **Scrollback snapshots** are captured periodically by [`src/core/scrollback-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/scrollback-store.ts) using `tmux capture-pane` and stored in the user data directory.
- **Cold start detection** relies on the `PtyCreateResult.fresh` flag in [`src/core/pty-manager.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/pty-manager.ts), triggering recovery logic in [`src/renderer/nodes/TerminalNode.tsx`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/nodes/TerminalNode.tsx).
- **Resume commands** are assembled by [`src/shared/agents/launch.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/launch.ts) using agent-specific grammars defined in [`src/shared/agents/config.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/agents/config.ts), enabling Claude, Codex, Gemini, and other agents to reload previous contexts.

## Frequently Asked Questions

### How does nodeterm know if a tmux session is lost?

The [`src/core/pty-manager.ts`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/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`](https://github.com/eneskirca/nodeterm/blob/main/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.