# Session Management for Long-Running Terminal Sessions in DesktopCommanderMCP

> Discover how DesktopCommanderMCP manages long-running terminal sessions efficiently. Learn about its PID-keyed TerminalManager, bounded buffers, and snapshot pagination for optimal performance.

- Repository: [Eduard Ruzga/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP)
- Tags: internals
- Published: 2026-08-06

---

**DesktopCommanderMCP handles long-running terminal sessions through a PID-keyed `TerminalManager` that maintains bounded output buffers (50 MiB), detects interactive prompts, and provides snapshot-based pagination to stream output efficiently without unbounded memory growth.**

DesktopCommanderMCP provides robust session management for long-running terminal sessions by tracking child processes through their entire lifecycle—from spawn to termination—while protecting against memory exhaustion through intelligent output eviction. The core implementation resides in [`src/terminal-manager.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/terminal-manager.ts), which maintains active sessions in memory and exposes APIs for paginated reads, interactive input detection, and forceful termination. This architecture enables the UI to monitor background processes, detect when commands enter interactive (blocked) states, and retrieve historical output without losing data to buffer overflows.

## Session Lifecycle and PID-Based Storage

### Creating Sessions with executeCommand()

When `executeCommand()` spawns a child process, it instantiates a `TerminalSession` object containing the process handle, line-based output buffers, timing metadata, and eviction counters. The session is immediately registered in the internal `sessions` Map using the process PID as the unique key.

```typescript
// src/terminal-manager.ts – session registration
this.sessions.set(childProcess.pid, session);

```

### Active vs. Completed Sessions

Active sessions live in `this.sessions` while their processes are running. Upon process exit, the manager copies the buffered output into a `CompletedSession` object and moves it to `this.completedSessions`. To prevent memory leaks, only the most recent **100 completed sessions** are retained; older entries are automatically purged.

```typescript
// src/terminal-manager.ts – move to completedSessions
this.completedSessions.set(childProcess.pid, {/*...*/});
this.sessions.delete(childProcess.pid);

```

## Memory-Safe Output Buffering

### The 50 MiB Buffer Cap

Each session maintains an `outputLines` array and tracks `bufferedChars` to enforce a hard limit defined by `MAX_BUFFERED_OUTPUT_CHARS` (50 MiB). When a session’s buffered output exceeds this threshold, the manager evicts the oldest lines first, updating `evictedLines` and `evictedChars` counters to maintain data integrity for clients.

```typescript
while (session.bufferedChars > MAX_BUFFERED_OUTPUT_CHARS && session.outputLines.length > 1) {
  const dropped = session.outputLines.shift()!;
  const droppedJoinedChars = dropped.length + 1;
  session.bufferedChars -= droppedJoinedChars;
  session.evictedChars += droppedJoinedChars;
  session.evictedLines++;
}

```

This eviction strategy prevents V8 string-length limits and ensures predictable memory usage even for indefinitely running commands like log tailers or REPLs.

## Detecting Interactive (Blocked) States

### Prompt Pattern Matching

The manager analyzes stdout and stderr streams against regex patterns to detect when a process enters an interactive state. When patterns like `>>>`, `>`, `$`, or `#` are detected at the end of output, `session.isBlocked` is set to `true` and the command promise resolves early, allowing the UI to present an input box.

```typescript
const quickPromptPatterns = />>>\s*$|>\s*$|\$\s*$|#\s*$/;
if (quickPromptPatterns.test(text)) {
  session.isBlocked = true;
  resolveOnce({ pid, output, isBlocked: true });
}

```

### Periodic Process Analysis

A `setInterval` periodically invokes `analyzeProcessState()` from [`src/utils/process-detection.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/process-detection.ts) to apply heuristic detection for prompts that may evade simple regex matching. This dual-layer approach catches both standard shells (Bash, Zsh, PowerShell) and custom REPL behaviors.

## Output Retrieval and Pagination

### Paginated Reads with readOutputPaginated()

The `readOutputPaginated(pid, offset, length)` method returns sliced output along with metadata including total lines, remaining lines, eviction information, exit code, and runtime. The offset parameter supports intelligent positioning:

- `offset = 0` reads from the last-read position (the "new output" view)
- Negative offsets fetch tails of the buffer (e.g., `offset = -100` returns the last 100 lines)

```typescript
// src/terminal-manager.ts – pagination entry point
readOutputPaginated(pid: number, offset = 0, length = 1000): PaginatedOutputResult | null

```

### Snapshot-Based Incremental Updates

For efficient UI polling without resending entire buffers, the manager provides snapshot-based APIs:

- `captureOutputSnapshot(pid)` records the total character count and line count at a specific moment, including evicted characters.
- `getOutputSinceSnapshot(pid, snapshot)` returns only the new output generated after the snapshot, correctly handling cases where output was evicted between polls.

```typescript
captureOutputSnapshot(pid)   // src/terminal-manager.ts#L658-L670
getOutputSinceSnapshot(pid, snapshot) // src/terminal-manager.ts#L682-L690

```

## Process Interaction and Termination

### Sending Input to Running Processes

The `sendInputToProcess(pid, input)` method writes data to the child’s stdin, automatically appending a newline if the input lacks one. This enables the UI to respond to `isBlocked` states by injecting user commands into interactive shells.

```typescript
// src/terminal-manager.ts – send input
sendInputToProcess(pid, input)   // lines 57-71

```

### Graceful and Forceful Termination

`forceTerminate(pid)` implements a two-phase shutdown strategy: it first sends `SIGINT` to allow the process to exit cleanly, then escalates to `SIGKILL` after a short delay if the process remains alive. This approach respects application cleanup hooks while preventing zombie processes.

```typescript
// src/terminal-manager.ts – termination
forceTerminate(pid)              // lines 117-131

```

## RPC Layer Integration

The `TerminalManager` API is exposed to the frontend through [`src/handlers/terminal-handlers.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/handlers/terminal-handlers.ts), which wraps these methods in RPC endpoints such as `run_process`, `read_process_output`, and `send_process_input`. This abstraction allows client applications to manage long-running terminal sessions without direct process manipulation.

## Summary

- **PID-keyed session storage**: Active sessions are indexed by process PID in `this.sessions`, with completed sessions archived to `this.completedSessions` (retaining only the most recent 100).
- **Bounded memory usage**: A 50 MiB output buffer per session with automatic eviction of oldest lines and tracking of evicted content via `evictedLines` and `evictedChars` counters.
- **Interactive state detection**: Regex patterns (`/>>>\s*$|>\s*$|\$\s*$|#\s*$/`) and periodic heuristics from `analyzeProcessState()` detect REPL prompts, setting `isBlocked` to enable user input flows.
- **Efficient output streaming**: Snapshot-based pagination (`captureOutputSnapshot`, `getOutputSinceSnapshot`) and `readOutputPaginated` deliver incremental updates without full buffer copies.
- **Process control**: `sendInputToProcess` and `forceTerminate` provide safe interaction mechanisms and guaranteed cleanup for background tasks.

## Frequently Asked Questions

### How does DesktopCommanderMCP prevent memory exhaustion from verbose command output?

Each session enforces a 50 MiB limit (`MAX_BUFFERED_OUTPUT_CHARS`) on buffered output. When exceeded, the manager automatically evicts the oldest lines from the buffer while incrementing `evictedChars` and `evictedLines` counters. This ensures memory usage remains bounded regardless of command verbosity, while metadata preserves awareness of truncated content.

### What happens to terminal sessions after the process exits?

Completed sessions move from the active `sessions` Map to `completedSessions`, which implements a fixed-size cache retaining only the 100 most recent sessions. Older completed sessions are automatically purged to prevent unbounded memory growth while maintaining reasonable history for debugging and audit purposes.

### How can the UI detect when a long-running command is waiting for user input?

The manager monitors stdout/stderr for prompt patterns using the regex `/>>>\s*$|>\s*$|\$\s*$|#\s*$/` and periodic calls to `analyzeProcessState()` from [`src/utils/process-detection.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/utils/process-detection.ts). When a prompt is detected, `session.isBlocked` is set to `true`, causing `executeCommand()` to resolve early and signal the UI to display an input prompt.

### Can I retrieve output that was generated before I started polling?

Yes. The `readOutputPaginated()` method supports negative offsets (e.g., `-100`) to fetch the last N lines of output. Additionally, `captureOutputSnapshot()` establishes a baseline of character and line counts, allowing `getOutputSinceSnapshot()` to retrieve only new content while accounting for any lines evicted between polls.