# How Nodeterm Persists Terminal Sessions Even When No Client Is Attached

> Discover how Nodeterm persists terminal sessions using tmux and disk snapshots. Keep your sessions running through detachments, restarts, and reboots.

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

---

**Nodeterm persists terminal sessions by leveraging tmux as a background server process paired with disk-based scrollback snapshots, allowing sessions to survive client detachments, app restarts, and even system reboots.**

Nodeterm, an open-source terminal workspace manager from the `eneskirca/nodeterm` repository, solves the problem of terminal session persistence through a hybrid architecture that combines process-level durability with visual state recovery. Unlike standard terminal emulators that terminate processes when the UI closes, Nodeterm maintains active sessions in the background using tmux and periodically saves terminal history to disk. This approach ensures that your terminal state remains intact whether you are switching between projects, closing the application, or restarting your machine.

## The Tmux Foundation for Session Persistence

### Session Naming and Identification

Every terminal node in Nodeterm receives a stable **node ID** (`persistKey`) that serves as the canonical identifier for the underlying tmux session. The system maps this ID to a tmux session name using the prefix `nt-`, resulting in session names like `nt-<nodeId>`.

In [`src/core/pty-manager.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/pty-manager.ts), the PTY manager constructs the tmux command:

```typescript
// The tmux command that enables persistence
const tmuxCmd = `tmux new-session -A -D -s nt-${nodeId}`;

```

The `-A` flag instructs tmux to **attach to an existing session** if it already exists, while `-D` detaches any other clients and `-s` specifies the session name. This single command handles both session creation and re-attachment atomically.

### Process Independence

When tmux is available, it runs as a **long-living server process** independent of the Nodeterm renderer process. This means that even when the Electron app quits or the terminal node moves off-screen (triggering a PTY client detach), the tmux server continues hosting the session. All running processes, environment variables, and scrollback history remain resident in memory until the tmux server itself terminates.

If tmux is not installed on the host system, Nodeterm gracefully falls back to a plain shell subprocess, though this mode does not provide persistence beyond the application lifecycle.

## Warm Re-attachment vs. Cold Start Recovery

### Warm Re-attachment (Existing Tmux Session)

When Nodeterm restarts or reconnects to an existing terminal node, the PTY manager detects that the tmux session is still active in [`src/core/pty-manager.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/pty-manager.ts). By invoking `tmux new-session -A`, the application re-attaches to the live session, immediately restoring the exact terminal state including all running processes and command history. The `fresh` flag is set to `false` during this operation, indicating that the session was preserved rather than created anew.

### Cold Start Recovery (Fresh Session Initialization)

In scenarios where the tmux server has terminated—such as after a machine reboot—Nodeterm detects a **cold start** condition (`fresh === true`). While the underlying tmux session is gone along with its in-memory state, Nodeterm restores the visual terminal history by reading from persistent storage.

The restoration logic resides in [`src/core/scrollback-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/scrollback-store.ts), which manages periodic snapshots of terminal output. These snapshots are stored in the user's data directory under `<userData>/terminal-scrollback/`.

```typescript
// Restoring scrollback after a cold start
async function restoreIfNeeded(nodeId: string) {
  const scrollback = await pty.readScrollback(nodeId);
  if (scrollback) {
    xterm.write(scrollback + '\r\n--- session restored ---\r\n');
  }
}

```

## Scrollback Snapshot Architecture

### Periodic State Capture

The [`src/core/scrollback-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/scrollback-store.ts) module continuously monitors active terminal sessions, writing scrollback buffers to disk at intervals defined by `SCROLLBACK_SNAPSHOT_MS`. This ensures that even if the application crashes or the system loses power, the terminal history is preserved up to the last snapshot interval.

### Manual Snapshot on Detach

In addition to periodic saves, Nodeterm triggers an immediate scrollback snapshot when the PTY client detaches. This captures the final terminal state before the renderer disconnects, minimizing data loss during unexpected closures.

## Agent State Persistence

Beyond raw terminal I/O, Nodeterm maintains the state of integrated AI agents such as Claude or Codex. The system checks if a node's `agentId` belongs to the `RESUMABLE_AGENTS` registry. For qualifying agents, Nodeterm constructs a resume command using the `--resume <sessionId>` flag, allowing the agent CLI to reconnect to its previous context after a cold start. This mechanism ensures that long-running AI coding sessions survive application restarts alongside their terminal environments.

## Implementation Example

The following code demonstrates how Nodeterm handles terminal creation, warm re-attachment, and scrollback restoration:

```typescript
// Create a terminal node – the node ID becomes the tmux session name
import { createTerminalNode } from 'src/renderer/state/workspace';

const node = createTerminalNode({
  id: 'node-123',               // persistKey → tmux session "nt-node-123"
  title: 'My Shell',
  cwd: '/home/user/project',
});

// Component mounts and attaches to tmux session
useEffect(() => {
  transport.create(node.id, {
    cwd: node.cwd,
    // `fresh` is false when re-attaching to an existing tmux session
    fresh: false, 
  });
}, [node.id]);

// Cold restart restoration logic
import { pty } from 'window.nodeTerminal';

async function restoreAfterReboot(nodeId: string) {
  const scrollback = await pty.readScrollback(nodeId);
  if (scrollback) {
    xterm.write(scrollback + '\r\n--- session restored ---\r\n');
  }
}

```

## Key Files and Architecture

- **[`src/core/pty-manager.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/pty-manager.ts)** – Core PTY manager that creates and attaches tmux sessions, determines warm vs. cold starts via the `fresh` flag, and orchestrates process lifecycle management.

- **[`src/core/scrollback-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/scrollback-store.ts)** – Implements the persistence layer for terminal visual state, handling periodic snapshots and `readScrollback` operations for cold start recovery.

- **[`src/main/node-pty-patch.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/node-pty-patch.test.ts)** – Test suite verifying that the native `node-pty` module correctly supports tmux integration before rebuild.

- **[`src/renderer/nodes/TerminalNode.tsx`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/nodes/TerminalNode.tsx)** – React component responsible for mounting the xterm instance, interfacing with the PTY transport layer, and triggering scrollback injection during restoration.

## Summary

- **Nodeterm uses tmux** as a background server to keep terminal processes alive independently of the UI client.
- **Session names** are derived from stable node IDs (`nt-<nodeId>`), enabling reliable re-attachment using `tmux new-session -A`.
- **Warm re-attachment** occurs when reconnecting to an existing tmux session, preserving all process state instantly.
- **Cold start recovery** relies on scrollback snapshots stored in `<userData>/terminal-scrollback/` to restore visual terminal history after system reboots.
- **Agent persistence** is maintained through resume flags for supported AI agents, ensuring coding context survives restarts.
- The architecture balances **process durability** (via tmux) with **visual state durability** (via disk snapshots) to provide seamless terminal persistence.

## Frequently Asked Questions

### Does Nodeterm require tmux to persist terminal sessions?

No, but tmux is required for full persistence capabilities. Without tmux, Nodeterm falls back to a standard shell subprocess that terminates when the application closes. Tmux provides the server-side infrastructure that keeps sessions running in the background after the client detaches.

### Where does Nodeterm store terminal scrollback data?

Scrollback snapshots are stored in the `<userData>/terminal-scrollback/` directory on disk, where `<userData>` represents the application's user data folder. The [`src/core/scrollback-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/scrollback-store.ts) module manages these files, writing snapshots periodically and reading them during cold start recovery.

### How does Nodeterm handle machine reboots?

After a machine reboot, the tmux server process no longer exists, causing Nodeterm to detect a cold start (`fresh === true`). The application then reads the last scrollback snapshot from disk via `pty.readScrollback()` and injects it into the xterm instance, displaying a "session restored" separator to indicate the recovery boundary.

### What happens to running processes when I close the Nodeterm window?

When the window closes or a terminal node moves off-screen, the PTY client detaches but the tmux server continues running. Any active processes within the session keep executing in the background. When you reopen Nodeterm or revisit the node, the application re-attaches to the existing tmux session, showing the current process state exactly as it progressed while disconnected.