How Freebuff Detects and Handles Parent Process Termination in Its Terminal Command Broker
Freebuff's terminal command broker detects parent process termination by monitoring its stdin pipe for EOF events, which fire when the parent CLI exits and closes the pipe, then classifies the failure and exits cleanly with code 1.
The Freebuff CLI spawns a terminal command broker as a child process to execute long-running operations like code generation and test execution. This broker must shut down automatically when the parent CLI terminates unexpectedly—whether from a user abort, terminal close, or crash—to prevent orphaned processes. The detection mechanism relies on UNIX pipe semantics and is implemented in the CodebuffAI/freebuff repository.
Pipe-Based Detection Mechanism
The broker does not use OS-specific process monitoring APIs. Instead, it exploits a fundamental property of pipes: when the writing process (the parent CLI) exits, the reading process (the broker) receives an end-of-file (EOF) on its standard input stream.
The Stdin Monitor Loop
In cli/src/utils/terminal-command-broker.ts, the serveTerminalCommandBroker() function starts the broker and immediately begins listening on process.stdin:
// From cli/src/utils/terminal-command-broker.ts
process.stdin.on('end', () => {
handleBrokerFailure({
stage: 'stdio',
error: new Error('stdin closed'),
});
});
The stdin stream carries JSON-encoded BrokerProtocol messages during normal operation. When the parent terminates, the pipe closes and the 'end' event fires without warning.
Keep-Alive Heartbeat
The parent CLI maintains pipe liveness through periodic writes. In createTerminalCommandBroker() at line 355, the parent establishes a heartbeat that writes small keep-alive tokens:
// Parent-side keep-alive mechanism
const heartbeat = setInterval(() => {
broker.stdin.write(JSON.stringify({ type: 'ping' }) + '\n');
}, 1000);
This serves dual purposes: it prevents the pipe from timing out due to inactivity, and it ensures that any parent crash manifests as an observable pipe closure rather than a stalled connection.
Failure Classification and Reporting
Once EOF is detected, the broker executes a structured failure handling pipeline defined in TerminalBrokerFailureStage.
Stage Enumeration
The TerminalBrokerFailureStage type at line 27 categorizes where failures originate:
type TerminalBrokerFailureStage =
| 'stdio' // stdin/stdout pipe issues
| 'protocol' // JSON parsing or message validation
| 'execution' // Command runtime errors
| 'internal'; // Unexpected exceptions
Parent termination always maps to the 'stdio' stage.
Error Classification Function
The classifyTerminalBrokerFailure() function at line 52 inspects the error and stage to produce a TerminalBrokerFailureCode:
function classifyTerminalBrokerFailure(
stage: TerminalBrokerFailureStage,
error: Error
): TerminalBrokerFailureCode {
if (stage === 'stdio' && error.message.includes('stdin closed')) {
return 'ParentProcessTerminated';
}
// ... other classifications
}
This classification distinguishes parent termination from other stdio failures like protocol corruption or premature stream closure.
Telemetry and Exit Sequence
The reportTerminalBrokerFailure() function at line 74 performs three operations:
- Log the failure with structured telemetry including stage, code, and timestamp
- Emit a final BrokerProtocol message to any listening parent (best-effort)
- Call
process.exit(1)to terminate immediately
function reportTerminalBrokerFailure(params: {
stage: TerminalBrokerFailureStage;
failureCode: TerminalBrokerFailureCode;
}): never {
telemetry.record({
event: 'broker_failure',
stage: params.stage,
code: params.failureCode,
});
process.exit(1);
}
The never return type ensures TypeScript recognizes this path as terminal.
Error Propagation to SDK Callers
Beneath the broker, the Freebuff SDK surfaces these failures through its public API. The run() function in sdk/src/run.ts at line 308 receives broker errors and transforms them into user-facing diagnostics:
// sdk/src/run.ts
const result = await broker.run(command);
if (result.failureCode === 'ParentProcessTerminated') {
throw new RunError('Parent process terminated – command cancelled');
}
This propagation chain ensures that tools consuming the Freebuff SDK receive actionable error information rather than opaque process crashes.
Implementation Example
To configure a broker with custom parent termination handling:
import { createTerminalCommandBroker } from './utils/terminal-command-broker';
const broker = createTerminalCommandBroker({
reportFailure: ({ stage, failureCode }) => {
if (failureCode === 'ParentProcessTerminated') {
console.error('CLI disconnected — shutting down cleanly');
}
},
});
await run({
command: ['pytest', '-v'],
terminalCommandBroker: broker,
});
Key Source Files
| File | Purpose |
|---|---|
cli/src/utils/terminal-command-broker.ts |
Core broker implementation including serveTerminalCommandBroker(), classifyTerminalBrokerFailure(), and reportTerminalBrokerFailure() |
sdk/src/tools/run-terminal-command.ts |
TerminalCommandBroker interface definition |
sdk/src/run.ts |
High-level run() API that bridges broker errors to SDK consumers |
cli/src/entry.ts |
CLI entry point with broker mode detection |
cli/src/utils/__tests__/terminal-command-broker.test.ts |
Test coverage for parent termination scenarios |
Summary
- Detection method: EOF on
process.stdinwhen parent closes the pipe - Classification:
TerminalBrokerFailureStage = 'stdio'withTerminalBrokerFailureCode = 'ParentProcessTerminated' - Cleanup action: Telemetry logging followed by
process.exit(1) - SDK exposure:
run()transforms broker failures intoRunErrorexceptions - OS portability: Relies on standard pipe semantics, no platform-specific APIs required
Frequently Asked Questions
What triggers the parent process termination detection?
The broker detects parent termination when its process.stdin emits an 'end' event. This occurs because the parent CLI holds the write end of the pipe; when the parent exits, the operating system closes all its file descriptors, causing the read end in the broker to signal EOF.
Can the broker distinguish parent termination from other stdio errors?
Yes. The classifyTerminalBrokerFailure() function specifically checks for the 'stdin closed' error message combined with the 'stdio' stage to return ParentProcessTerminated. Other stdio failures produce different codes such as ProtocolCorruption or PrematureClose.
What happens if the parent crashes without closing stdin properly?
Even in crash scenarios, the operating system reclaims the parent's file descriptors. The kernel closes the write end of the pipe, which still triggers EOF on the broker's read end. This makes the mechanism robust against SIGKILL, SIGSEGV, and other abnormal terminations.
How does this compare to explicit heartbeat protocols?
Freebuff uses an implicit heartbeat through normal pipe operation rather than explicit health-check messages. The periodic keep-alive writes prevent false positives from idle timeouts, but the actual termination detection requires zero additional network or IPC overhead—just standard stream events.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →