How Codex-Plugin-CC Handles Process Termination Across Operating Systems
Codex-Plugin-CC uses platform-specific strategies to terminate process trees, employing taskkill on Windows and process-group signaling on Unix-like systems, with graceful fallbacks to prevent resource leaks.
The openai/codex-plugin-cc repository provides a robust cross-platform solution for managing child processes through its self-contained process library located in plugins/codex/scripts/lib/process.mjs. This implementation ensures reliable cleanup of process trees across Windows, macOS, and Linux without leaking system resources or leaving zombie processes.
Cross-Platform Process Termination Architecture
Core Implementation in process.mjs
The termination logic centers on the terminateProcessTree(pid, options) function exported from plugins/codex/scripts/lib/process.mjs. This helper validates input parameters—returning early if pid is not a finite number—and delegates to platform-specific implementations based on process.platform or an optional override.
Platform Detection and Overrides
By default, the library detects the operating system via process.platform, but callers can specify options.platform to force Windows (win32) or Unix-like behavior. This injection capability supports unit testing by allowing developers to simulate different environments without modifying global state.
Windows Process Termination Strategy
Primary taskkill Approach
On Windows platforms, the library executes taskkill /PID <pid> /T /F through the internal runCommand wrapper, which forces shell usage to resolve built-in commands. The /T flag terminates the entire process tree, while /F forces immediate termination. Success (status === 0) returns a result object with method: "taskkill".
Handling Missing Processes and Fallbacks
If taskkill reports that the target process does not exist—detected via the looksLikeMissingProcessMessage regex helper—the function returns attempted: true with delivered: false. When the taskkill binary itself is missing (ENOENT error), the library falls back to Node's native process.kill(pid), setting method: "kill". Any other taskkill errors are re-thrown to preserve exception context for upstream handlers.
Unix-like Process Termination Strategy
Process Group Signaling
For macOS, Linux, and other POSIX systems, the primary strategy sends SIGTERM to the entire process group using process.kill(-pid, "SIGTERM"). The negative PID signals all processes sharing the same group ID, effectively terminating the whole tree in one operation. This approach is recorded with method: "process-group".
Individual Process Fallback
When the group kill fails with ESRCH (no such process group), the library falls back to signaling the individual PID via process.kill(pid, "SIGTERM"). If this secondary attempt also fails with ESRCH, the result indicates the signal could not be delivered. Non-ESRCH errors from the initial group kill trigger the same fallback logic.
API Return Structure and Error Handling
Every termination attempt returns a uniform object regardless of platform:
{
attempted: true|false,
delivered: true|false,
method: "taskkill" | "kill" | "process-group" | "process" | null,
result: <spawnSync result> // present only for taskkill path
}
This consistent shape allows higher-level components like codex-companion.mjs to reason about termination status without platform-specific branching. The runCommand function normalizes execution options including working directory, environment variables, and stdio handling, while formatCommandFailure generates human-readable error messages from failed spawnSync results.
Practical Implementation Examples
The following examples demonstrate the public API for terminating processes and mocking dependencies:
import { terminateProcessTree } from "./plugins/codex/scripts/lib/process.mjs";
/* Terminate a Unix-style process tree */
const result = terminateProcessTree(12345);
console.log(`Method: ${result.method}, Delivered: ${result.delivered}`);
/* Force Windows behavior for testing */
const winResult = terminateProcessTree(6789, { platform: "win32" });
/* Inject custom command runner for unit testing */
const mockRun = (cmd, args) => ({ error: { code: "ENOENT" }, status: null });
const mocked = terminateProcessTree(5555, { runCommandImpl: mockRun });
Summary
- Platform abstraction: The
terminateProcessTreefunction inplugins/codex/scripts/lib/process.mjsautomatically selects Windows or Unix termination strategies based onprocess.platform. - Windows execution: Uses
taskkill /T /Ffor complete tree termination, falling back toprocess.killonly when the binary is missing. - Unix signaling: Prioritizes process-group
SIGTERM(-pid) for batch termination, with individual PID fallback onESRCHerrors. - Consistent API: Returns uniform result objects with
attempted,delivered, andmethodfields across all platforms and error conditions. - Testability: Supports platform overrides via
options.platformand dependency injection viaoptions.runCommandImpl.
Frequently Asked Questions
How does codex-plugin-cc terminate processes on Windows?
On Windows, it spawns taskkill /PID <pid> /T /F to forcefully terminate the process and its children. If taskkill is unavailable, it falls back to Node's process.kill(pid). The function detects missing processes via regex matching against taskkill output through the looksLikeMissingProcessMessage helper.
What happens when terminating a non-existent process on Linux?
The library first attempts process.kill(-pid, "SIGTERM") on the process group. If this returns ESRCH (no such process), it falls back to signaling the individual PID. If both fail with ESRCH, the result object contains attempted: true and delivered: false, allowing callers to distinguish between "process already gone" and "termination failed."
Can I test the termination logic without spawning real processes?
Yes. The terminateProcessTree function accepts options.runCommandImpl to override the command execution function, and options.platform to force specific OS behavior. This allows unit tests to mock taskkill responses or simulate ENOENT errors without shell execution.
Where does the high-level Codex companion use this termination logic?
The codex-companion.mjs script imports these utilities to manage child processes during plugin execution. It relies on the uniform return values from terminateProcessTree to determine whether cleanup succeeded before proceeding with subsequent operations.
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 →