# How Freebuff Ensures Process Isolation for Terminal Commands

> Discover how Freebuff ensures process isolation for terminal commands. It uses a detached broker process for a separate execution environment, enhancing security and stability.

- Repository: [Codebuff/freebuff](https://github.com/CodebuffAI/freebuff)
- Tags: internals
- Published: 2026-09-01

---

**Freebuff guarantees process isolation for every terminal command by delegating execution to a detached "broker" helper process that creates a completely separate execution environment from the main CLI.**

Process isolation is critical for AI-powered developer tools that execute arbitrary shell commands. In the CodebuffAI/freebuff repository, this isolation is implemented through a sophisticated broker pattern centered in [`cli/src/utils/terminal-command-broker.ts`](https://github.com/CodebuffAI/freebuff/blob/main/cli/src/utils/terminal-command-broker.ts). This architecture prevents rogue commands from crashing the interactive CLI, leaking resources, or leaving orphaned subprocesses behind.

## The Broker Architecture for Terminal Command Isolation

Freebuff's isolation strategy revolves around a **one-shot broker process** that acts as a protective intermediary between the main CLI and the user's command. The broker is spawned with specific flags and environment variables that signal its special purpose.

### Broker Invocation Mechanism

The parent CLI initiates isolation by spawning itself with the `--terminal-command-broker` flag and setting `CODEBUFF_TERMINAL_COMMAND_BROKER=1`:

```typescript
// From cli/src/utils/terminal-command-broker.ts #L17-L22
const broker = spawn(process.execPath, [
  '--terminal-command-broker',
  // ... other args
], {
  env: {
    ...process.env,
    CODEBUFF_TERMINAL_COMMAND_BROKER: '1',
  },
  detached: true,  // Critical for isolation
})

```

This pattern allows the same Node.js/Bun binary to serve dual purposes: interactive CLI or isolated command executor, determined entirely by environment state.

## Protocol File Communication

Commands and results flow through **atomic temporary JSON files** rather than persistent pipes or sockets.

### One-Shot Write Semantics

The broker creates uniquely-named protocol files using the pattern `freebuff-terminal-command-broker-<pid>-<uuid>.json` in the OS temp directory. These files use the `wx` flag to guarantee **exclusive creation**:

```typescript
// From cli/src/utils/terminal-command-broker.ts #L41-L48
const protocolPath = path.join(
  tmpdir(),
  `freebuff-terminal-command-broker-${process.pid}-${randomUUID()}.json`
)

// Atomic write prevents race conditions
writeFileSync(protocolPath, JSON.stringify(result), { flag: 'wx' })

```

This ensures that even if multiple brokers launch simultaneously, their protocol files never collide.

### Parent Disconnection Detection

The broker continuously monitors its parent PID to detect premature CLI termination. From lines 71-96 of [`terminal-command-broker.ts`](https://github.com/CodebuffAI/freebuff/blob/main/terminal-command-broker.ts), the broker polls the parent process and triggers **immediate self-termination** of its entire process group if the parent vanishes:

```typescript
// Simplified from the source
const checkParentAlive = setInterval(() => {
  try {
    process.kill(parentPid, 0)  // Signal 0 tests existence
  } catch (e) {
    // Parent dead — cleanup and exit
    killProcessGroup(process.pid)
    clearInterval(checkParentAlive)
  }
}, PARENT_CHECK_INTERVAL_MS)

```

This prevents the common "zombie broker" problem where detached processes outlive their creator.

## Process Tree Management

### Detached Process Grouping

The broker itself spawns with `detached: true` while the **user command runs with `detached: false`**:

```typescript
// From cli/src/utils/terminal-command-broker.ts #L99-L106
const child = spawn(command.executable, command.args, {
  stdio: ['pipe', 'pipe', 'pipe'],
  cwd: command.cwd,
  env: command.env,
  detached: false,           // Child belongs to broker's group
  windowsHide: true,
})

// Broker is detached, so we can kill its entire group later

```

This hierarchy allows the broker to terminate the user's command tree with a **single signal to its own process group** rather than tracking individual PIDs.

### Cross-Platform Process Termination

Cleanup logic adapts to platform capabilities. Lines 198-215 of [`terminal-command-broker.ts`](https://github.com/CodebuffAI/freebuff/blob/main/terminal-command-broker.ts) show the **graceful-then-forceful termination sequence**:

```typescript
// From cli/src/utils/terminal-command-broker.ts #L200-L207
if (process.platform === 'win32') {
  // Windows: taskkill.exe forcibly terminates entire tree
  spawn('taskkill.exe', ['/pid', pid.toString(), '/T', '/F'])
} else {
  // Unix: SIGTERM first, then SIGKILL
  try {
    process.kill(-pid, 'SIGTERM')  // Negative PID = process group
    await setTimeout(TERMINATION_GRACE_PERIOD_MS)
    process.kill(-pid, 'SIGKILL')
  } catch {}
}

```

The negative PID (`-pid`) syntax targets the **entire process group**, ensuring no grandchild processes escape termination.

## Stdio Safety on Windows

Freebuff deliberately **avoids custom file descriptor pipes** on Windows due to Bun runtime limitations. Lines 88-95 of the broker implementation document this defense:

```typescript
// From cli/src/utils/terminal-command-broker.ts #L88-L95
// Only use standard stdio pipes
const stdio: StdioOptions = ['pipe', 'pipe', 'pipe']

// Custom file descriptors disabled on Windows — Bun's implementation
// can cause unhandled rejections that crash the CLI process
if (process.platform !== 'win32') {
  // stdio.push('pipe')  // Intentionally omitted
}

```

This conservative approach sacrifices some flexibility for **absolute CLI stability**.

## Error Classification and Telemetry

All failure modes are **explicitly categorized** to enable rapid debugging of isolation failures. The broker distinguishes:

- `ENOENT` — executable not found
- `EACCES` — permission denied
- `EIO`, `EPIPE` — stdio stream errors
- Exit code capture for non-zero exits

These classifications feed into analytics (lines 52-71) so the Freebuff team can surface isolation-related regressions before they affect users.

## Secure Cleanup Guarantees

The broker's teardown is **resilient to partial failures**:

```typescript
// From cli/src/utils/terminal-command-broker.ts #L48-L55
try {
  rmSync(protocolPath, { force: true })  // force ignores missing file
} finally {
  // Always attempt process group termination
  killProcessGroup(process.pid)
}

```

The `force: true` option ensures cleanup doesn't throw if the protocol file was already removed, and the `finally` block guarantees termination logic runs even if cleanup throws.

## Complete Usage Example

Here's how to execute an isolated command through the broker:

```typescript
import { terminalCommandBroker } from '@codebuff/sdk'

const process = terminalCommandBroker.start({
  executable: 'npm',
  args: ['install', 'typescript'],
  cwd: '/my/project',
  env: { ...process.env, NODE_ENV: 'production' },
})

// Stream output to CLI
process.stdout.pipe(process.stdout)
process.stderr.pipe(process.stderr)

const exitCode = await process.completion
if (exitCode !== 0) {
  console.error(`Command failed with code ${exitCode}`)
}

```

Under the hood, this SDK call translates to the broker-spawning logic shown throughout this article.

## Summary

Freebuff's terminal command isolation rests on seven architectural pillars:

- **Broker delegation** — every command runs through a dedicated intermediary process
- **Atomic protocol files** — one-shot JSON files eliminate race conditions in IPC
- **Process group killing** — entire subprocess trees terminate together, preventing orphans
- **Parent death detection** — brokers self-terminate if the CLI crashes or is killed
- **Conservative stdio** — standard pipes only, avoiding Bun Windows instability
- **Explicit error taxonomy** — all failures categorized for observability
- **Defensive cleanup** — `finally` blocks and `force: true` ensure resources release

These mechanisms, implemented primarily in [`cli/src/utils/terminal-command-broker.ts`](https://github.com/CodebuffAI/freebuff/blob/main/cli/src/utils/terminal-command-broker.ts), allow Freebuff to safely execute arbitrary user commands without compromising the interactive CLI experience.

## Frequently Asked Questions

### What happens if the Freebuff CLI crashes while a broker is running?

The broker detects parent process death through PID polling (lines 71-96 of [`terminal-command-broker.ts`](https://github.com/CodebuffAI/freebuff/blob/main/terminal-command-broker.ts)). When the parent disappears, the broker immediately terminates its own process group, ensuring no orphaned processes remain even during abnormal CLI shutdown.

### Why does Freebuff use temporary files instead of pipes for broker communication?

Atomic file creation with the `wx` flag prevents race conditions when multiple brokers launch simultaneously. The one-shot write pattern (lines 64-68) also provides a natural durability boundary — the result persists even if the broker crashes immediately after writing.

### Does the broker isolation work on Windows, macOS, and Linux equally?

Yes, with platform-specific adaptations. Windows uses `taskkill.exe /T /F` for tree termination (lines 200-207), while Unix systems use negative PID signals to target process groups. The stdio configuration is intentionally conservative on Windows due to Bun runtime constraints.

### How does Freebuff prevent memory leaks from repeatedly spawning broker processes?

Each broker is **strictly one-shot** — it handles exactly one command, writes the result, cleans up its protocol file, and exits. The parent CLI reaps the broker through the `completion` promise. This finite lifecycle prevents accumulation of long-running helper processes.