# How the Terminal Command Broker Isolates Process Execution in Freebuff: A Deep Dive

> Discover how the terminal command broker in Freebuff isolates process execution using sandboxed processes, detached spawning, PID tracking, and IPC for secure CLI operations.

- Repository: [Codebuff/freebuff](https://github.com/CodebuffAI/freebuff)
- Tags: deep-dive
- Published: 2026-08-20

---

The **terminal command broker** in Freebuff creates a dedicated, sandboxed process for each command, ensuring complete isolation between the CLI runtime and spawned subprocesses through detached process spawning, PID tree tracking, and structured IPC communication.

Freebuff's CLI delegates every terminal invocation to a specialized broker rather than executing commands directly. This design prevents resource leaks, enables reliable cleanup, and keeps the main agent runtime stable regardless of what the executed command does. According to the Freebuff source code, the broker combines Node.js process primitives with careful tree management to achieve robust isolation.

## How the Terminal Command Broker Spawns Isolated Processes

### Detached Child Process Creation

The broker's core isolation mechanism starts with `child_process.spawn` using the `detached: true` option. In [`cli/src/utils/terminal-command-broker.ts`](https://github.com/CodebuffAI/freebuff/blob/main/cli/src/utils/terminal-command-broker.ts), the broker configures:

- **Independent process group**: The detached flag places the child in its own process group, decoupling its lifecycle from the parent
- **I/O redirection**: `stdio: ['ignore', 'pipe', 'pipe']` streams output back without blocking
- **Working directory isolation**: Each command receives its own `cwd` context

```typescript
// Simplified from terminal-command-broker.ts
const child = spawn(request.command, {
  cwd: request.cwd,
  detached: true,               // Critical isolation primitive
  stdio: ['ignore', 'pipe', 'pipe'],
});

```

### PID Hierarchy Tracking

The broker records not just the root PID, but enables enumeration of the entire descendant tree. This is essential for complete cleanup. The implementation stores:

- `rootPid`: The primary spawned process identifier
- Platform-specific tree walking logic to discover grandchildren

This tracking appears in [`sdk/src/tools/run-terminal-command.ts`](https://github.com/CodebuffAI/freebuff/blob/main/sdk/src/tools/run-terminal-command.ts), where the broker interface contract requires PID reporting for downstream cleanup.

## The Terminal Command Broker API: Start and Kill Operations

### start(request): Launching Isolated Commands

The broker exposes a `start(request)` method that bridges SDK calls to raw process spawning. Located in [`cli/src/utils/terminal-command-broker.ts`](https://github.com/CodebuffAI/freebuff/blob/main/cli/src/utils/terminal-command-broker.ts), this method:

1. Validates the `TerminalCommandRequest` (command string, working directory)
2. Spawns the detached process with shell wrapper handling
3. Attaches output stream listeners for `stdout`/`stderr` capture
4. Returns a handle containing the PID and stream references

The SDK consumes this through [`sdk/src/run.ts`](https://github.com/CodebuffAI/freebuff/blob/main/sdk/src/run.ts), which passes the broker instance into `runTerminalCommand` calls:

```typescript
// From sdk/src/run.ts usage pattern
await runTerminalCommand({
  command: 'npm install',
  cwd: '/my/project',
  terminalCommandBroker,        // Injected isolation layer
});

```

### kill(): Terminating the Entire Process Tree

The broker's `kill()` method guarantees no orphaned processes through negative PID signaling. On Unix systems:

```typescript
// Terminal command broker kill implementation
async kill() {
  if (this.rootPid) {
    // Negative PID targets the entire process group
    process.kill(-this.rootPid, 'SIGTERM');
    // Escalate to SIGKILL if graceful termination fails
  }
}

```

This **process group termination** is the key isolation guarantee—every descendant spawned by the command receives the signal simultaneously.

## IPC and Structured Output Relay

### Standard I/O Streaming

The broker runs in its own Node.js instance and communicates with the CLI via streams. From [`cli/src/utils/terminal-command-broker.ts`](https://github.com/CodebuffAI/freebuff/blob/main/cli/src/utils/terminal-command-broker.ts):

- `child.stdout` and `child.stderr` pipe through transform streams
- Output chunks accumulate with byte limits for memory safety
- Exit codes are captured via `child.on('close', ...)` listeners

### Structured Payload Delivery

Results flow back as `CodebuffToolOutput<'run_terminal_command'>`—a typed envelope containing:

- `stdout`: Captured standard output string
- `stderr`: Captured standard error string
- `exitCode`: Numeric process termination status
- `pid`: The root process identifier for auditing

This structured format appears in [`sdk/src/tools/run-terminal-command.ts`](https://github.com/CodebuffAI/freebuff/blob/main/sdk/src/tools/run-terminal-command.ts), which defines the broker interface contract.

## Timeout Enforcement and Cancellation Guarantees

### Signal Cascade for Runaway Prevention

The broker implements two-tier termination:

1. **Graceful shutdown**: `SIGTERM` with configurable timeout
2. **Force kill**: `SIGKILL` escalation when graceful fails

The terminal command broker tracks cancellation state internally, allowing the CLI to abort long-running operations while still receiving partial output captured before termination.

### User-Initiated Abort Handling

When the CLI receives a user abort signal, it delegates to the broker's `kill()` method rather than attempting direct process manipulation. This centralizes cleanup logic and ensures consistent behavior across platforms.

## Cross-Platform Process Isolation

### Windows Shell Wrapping

On Windows, the broker spawns commands through `cmd.exe` or PowerShell, maintaining the `detached: true` semantics via Node.js libuv abstraction. The process group concept maps to Windows job objects internally.

### Unix Direct Execution

On macOS and Linux, the broker executes directly under `/bin/bash` when shell interpretation is needed, or passes the command array directly for binary execution. The negative PID kill pattern relies on POSIX process group semantics.

Platform detection logic resides in [`cli/src/utils/terminal-command-broker.ts`](https://github.com/CodebuffAI/freebuff/blob/main/cli/src/utils/terminal-command-broker.ts), with test coverage in [`cli/src/utils/__tests__/terminal-command-broker.test.ts`](https://github.com/CodebuffAI/freebuff/blob/main/cli/src/utils/__tests__/terminal-command-broker.test.ts) verifying isolation behavior on both platforms.

## Practical Implementation Example

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

// Execute with guaranteed isolation
const result = await runTerminalCommand({
  command: 'long-running-build.sh',
  cwd: '/project',
  timeoutMs: 300_000,           // 5 minute broker-enforced limit
  terminalCommandBroker,        // Isolation provider
});

// result.pid can be audited
// result.exitCode indicates success/failure
// No orphaned processes regardless of outcome

```

## Summary

- **Detached spawning** via `child_process.spawn` creates independent process groups for each command
- **PID tree tracking** enables complete enumeration and cleanup of all descendant processes
- **Start/kill API** provides explicit lifecycle control with guaranteed termination capability
- **Structured IPC** delivers typed output through standard streams without blocking the main runtime
- **Signal cascade** ensures reliable cleanup even for misbehaving or indefinitely hanging commands
- **Platform abstraction** maintains consistent isolation semantics across Windows and Unix systems

## Frequently Asked Questions

### What happens if a terminal command spawns its own child processes?

The terminal command broker tracks the root PID and uses process group signaling. On Unix, `process.kill(-pid)` targets the entire group including grandchildren. The broker's `kill()` implementation enumerates descendants when needed, ensuring no orphaned processes survive cancellation.

### How does the broker handle commands that ignore SIGTERM?

The broker implements escalation timing: after sending `SIGTERM`, it monitors for process exit. If the process remains after the grace period (typically configurable), it sends `SIGKILL` which cannot be caught or ignored. This guarantees termination regardless of signal handling in the spawned command.

### Can the broker stream output in real-time, or only at completion?

The broker pipes `stdout` and `stderr` through transform streams immediately as data arrives. While the SDK's `runTerminalCommand` may buffer for final delivery, the underlying broker streams are live. Real-time streaming requires consuming the broker's stream interfaces directly rather than the promise-based wrapper.

### Where is the broker interface defined versus its implementation?

The **interface contract** lives in [`sdk/src/tools/run-terminal-command.ts`](https://github.com/CodebuffAI/freebuff/blob/main/sdk/src/tools/run-terminal-command.ts), defining `TerminalCommandBroker` types and `TerminalCommandRequest` shapes. The **concrete implementation** is in [`cli/src/utils/terminal-command-broker.ts`](https://github.com/CodebuffAI/freebuff/blob/main/cli/src/utils/terminal-command-broker.ts). This separation allows the SDK to remain agnostic while the CLI provides platform-specific isolation logic.