# How Codex-Plugin-CC Handles Process Termination Across Operating Systems

> Discover how Codex-Plugin-CC ensures reliable process termination on Windows and Unix-like systems using platform-specific strategies and graceful fallbacks to prevent resource leaks.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: internals
- Published: 2026-07-07

---

**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:

```js
{
  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:

```js
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 `terminateProcessTree` function in `plugins/codex/scripts/lib/process.mjs` automatically selects Windows or Unix termination strategies based on `process.platform`.
- **Windows execution**: Uses `taskkill /T /F` for complete tree termination, falling back to `process.kill` only when the binary is missing.
- **Unix signaling**: Prioritizes process-group `SIGTERM` (`-pid`) for batch termination, with individual PID fallback on `ESRCH` errors.
- **Consistent API**: Returns uniform result objects with `attempted`, `delivered`, and `method` fields across all platforms and error conditions.
- **Testability**: Supports platform overrides via `options.platform` and dependency injection via `options.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.