# How Nodeterm Manages Offscreen Terminal Memory and Resources

> Discover how Nodeterm efficiently manages offscreen terminal memory with visibility tracking, timers, and safety predicates. Learn about its resource management techniques.

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

---

**Nodeterm treats offscreen terminals as disposable viewers that can be released without killing the underlying tmux session, using a three-stage pipeline of visibility tracking, configurable timers, and safety predicates that defer disposal when active agents are present.**

Nodeterm, developed in the open-source repository `eneskirca/nodeterm`, implements sophisticated **offscreen terminal resource management** to prevent memory leaks in long-running desktop sessions. The architecture distinguishes between the visual *viewer* component and the persistent tmux session, enabling aggressive memory reclamation while preserving CLI state. This analysis examines the source code mechanisms that determine when and how terminals are safely disposed, parked, or hibernated.

## Visibility Tracking with IntersectionObserver

Each terminal node mounts a single `IntersectionObserver` that monitors viewport presence. When a node leaves the visible area, the observer marks it as offscreen via `setNodeOffscreen` in [`src/renderer/nodes/TerminalNode.tsx`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/nodes/TerminalNode.tsx).

The implementation maintains a global `offscreenNodes` set that records all offscreen IDs:

```tsx
// src/renderer/nodes/TerminalNode.tsx
if (offscreen) offscreenNodes.add(nodeId) else offscreenNodes.delete(nodeId);

```

This set is consulted during the hibernation sweep to determine which terminals are candidates for resource release. The observer remains mounted for the lifecycle of the node, providing real-time visibility state without polling overhead.

## Off-Screen Disposal Policy and Timer Configuration

The core disposal logic resides in [`src/renderer/terminal/offscreen-policy.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/terminal/offscreen-policy.ts). The system converts user settings into millisecond timeouts via `offscreenDisposeMs(settingMinutes)`, which processes `settings.offscreenTerminalMinutes` (defaulting to 10 minutes). A value of zero or negative disables the feature entirely.

The `planOffscreenVisibility` function evaluates four states for each node:

- **Arm** a dispose timer if the node is offscreen and no timer exists
- **Cancel** an existing timer if the node becomes visible
- **Revive** a previously disposed view if it returns to visibility
- **Defer** if safety predicates block disposal

```tsx
// Off-screen policy decision
const plan = planOffscreenVisibility({
  visible: isNodeWatched(id),
  down: offscreenDown,
  timerArmed: !!offscreenTimerRef.current,
  disposeMs: offscreenDisposeMs(settings.offscreenTerminalMinutes),
});
if (plan.armTimer) offscreenTimerRef.current = setTimeout(disposeViewer, plan.disposeMs);

```

Users control this behavior via **Settings → Terminal → "Off-screen minutes"** (`settings.offscreenTerminalMinutes`). Setting this to `0` causes `releaseStillEnabled` to return `false`, disabling automatic disposal.

## Safe Release: Live-Work and Eco Guards

Before tearing down a PTY client, Nodeterm evaluates two safety predicates to prevent interrupting active workflows.

**Live-Work Protection** ([`src/renderer/terminal/live-work.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/terminal/live-work.ts)): The `shouldDeferReleaseForLiveWork` check prevents disposal when the PTY is the actual shell (not tmux-backed) and a live agent (working, waiting, or blocked) is attached. The `wouldKillLiveWork` function returns true when `!i.tmuxBacked && !!i.agentState && LIVE_AGENT_STATES.has(i.agentState)`:

```ts
// src/renderer/terminal/live-work.ts
export function wouldKillLiveWork(i: LiveWorkInput): boolean {
  return !i.tmuxBacked && !!i.agentState && LIVE_AGENT_STATES.has(i.agentState);
}

```

**Eco Deferral** ([`offscreen-policy.ts`](https://github.com/eneskirca/nodeterm/blob/main/offscreen-policy.ts)): The `shouldDeferReleaseForEco` function adds a secondary guard when the Eco hibernation feature is active. It calculates a cap based on `idleMinutes + offscreenMinutes` to ensure that even non-hibernating agents are eventually released after a bounded window:

```ts
// src/renderer/terminal/offscreen-policy.ts
export function shouldDeferReleaseForEco(i: {
  ecoEnabled: boolean;
  resumableAgent: boolean;
  hibernated: boolean;
  idleKnown: boolean;
  offscreenElapsedMs: number;
  idleMinutes: number;
  offscreenMinutes: number;
}): boolean {
  if (!i.ecoEnabled || !i.resumableAgent || i.hibernated || !i.idleKnown) return false;
  if (!(i.idleMinutes > 0)) return false;
  const cap = (i.idleMinutes + Math.max(0, i.offscreenMinutes)) * 60_000;
  return i.offscreenElapsedMs < cap;
}

```

These guards ensure that AI agents like Claude Code or other long-running processes are not killed mid-turn when their terminal scrolls offscreen.

## Remote and Relay Session Exclusion

Offscreen disposal is bypassed for terminals whose sessions live on remote machines. The `offscreenCoreIsRemote(session.source)` function returns `true` for `relay` or `server` sources, causing `mayDisposeOffscreen` to return `false`.

This exclusion prevents attempts to resurrect remote tmux sessions while the SSH ControlMaster connection is down, which would otherwise render an offline overlay. The check ensures that only local terminal viewers are candidates for the offscreen disposal pipeline.

## Parked Terminals and LRU Caching

If a node is unmounted (e.g., during a project switch) before the offscreen timer fires, Nodeterm *parks* the PTY client rather than killing it immediately. The park system is implemented as a bounded LRU cache in [`src/renderer/terminal/park-budget.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/renderer/terminal/park-budget.ts) with a default lifetime of 5 minutes (`TERM_PARK_MS`).

When the park entry expires, `canDisposeParkedEntry` checks the same live-work predicate before finally terminating the PTY. This caching strategy allows users to pan back to a node quickly (warm re-attach) while still reclaiming memory for long-idle viewers. The park budget operates independently of the offscreen timer but shares the same safety constraints.

## Integration with Eco Hibernation

The offscreen release pipeline is ordered **after** Eco hibernation. If Eco is enabled, the release waits for the agent to be hibernated (`shouldDeferReleaseForEco`). The combined `idleMinutes + offscreenMinutes` cap guarantees that a node which never becomes hibernatable—such as a cron job or never-idle agent—will still be freed after a bounded period, preventing permanent memory leaks.

This layered approach ensures that **nodeterm offscreen terminal memory** management respects both aggressive power-saving policies and the requirement for bounded resource usage.

## Summary

- **Nodeterm** distinguishes between disposable terminal viewers and persistent tmux sessions, enabling memory reclamation without killing underlying processes.
- **Visibility tracking** uses a single `IntersectionObserver` per `TerminalNode` to populate the global `offscreenNodes` set.
- **Safety predicates** in [`live-work.ts`](https://github.com/eneskirca/nodeterm/blob/main/live-work.ts) and [`offscreen-policy.ts`](https://github.com/eneskirca/nodeterm/blob/main/offscreen-policy.ts) defer disposal when agents are active or when Eco hibernation is pending.
- **Remote sessions** are excluded from disposal to prevent SSH reconnection failures when ControlMaster is unavailable.
- **Parked terminals** live in a 5-minute LRU cache ([`park-budget.ts`](https://github.com/eneskirca/nodeterm/blob/main/park-budget.ts)) for warm re-attachment before final disposal.
- **User configuration** controls the disposal timer via `settings.offscreenTerminalMinutes`, set to 10 minutes by default.

## Frequently Asked Questions

### What happens to the tmux session when a terminal goes offscreen?

The tmux session persists. Nodeterm treats only the *viewer* component as disposable; releasing an offscreen terminal viewer detaches the local PTY client but leaves the underlying tmux session running. When you return to the terminal, Nodeterm reattaches to the existing session rather than creating a new one.

### How does Nodeterm prevent killing active AI agents during offscreen disposal?

The system checks `shouldDeferReleaseForLiveWork` before disposal. If the terminal is the actual shell (not tmux-backed) and an agent is in a working, waiting, or blocked state (as defined by `LIVE_AGENT_STATES` in [`live-work.ts`](https://github.com/eneskirca/nodeterm/blob/main/live-work.ts)), the disposal is deferred. Additionally, `shouldDeferReleaseForEco` waits for Eco hibernation to complete before releasing resumable agents.

### Can I disable automatic offscreen terminal disposal?

Yes. Set **Settings → Terminal → "Off-screen minutes"** to `0` or any negative value. This causes `releaseStillEnabled` to return `false` and disables the disposal timer entirely. Alternatively, set a very high value to delay disposal significantly while keeping the feature active for extremely long idle periods.

### What is the difference between parking and disposing a terminal?

**Parking** occurs when a terminal node is unmounted (e.g., switching projects) and stores the PTY client in an LRU cache for 5 minutes (`TERM_PARK_MS`), allowing instant reattachment. **Disposing** occurs when the offscreen timer fires or the park expires, and actually terminates the PTY client after checking live-work and Eco safety predicates. Parking is a temporary cache; disposal is permanent resource release.