# How the Codex Plugin Background Task Spawning Mechanism Utilizes Detached Processes

> Discover how the Codex plugin background task spawning mechanism uses detached processes for robust task execution, even after parent termination. Learn more about task-worker mode.

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

---

**The Codex plugin spawns background tasks by launching a detached Node.js child process that runs in "task-worker" mode, allowing the task to survive parent termination while preserving state for later status queries.**

The `openai/codex-plugin-cc` repository implements long-running task execution through a robust background task spawning mechanism that leverages Node.js detached processes. When users invoke the companion CLI with the `--background` flag, the system creates an independent process group that continues execution even if the initiating terminal session ends. This architecture ensures that complex Codex operations can run asynchronously without blocking the main process or risking termination when the parent exits.

## Detecting the Background Execution Flag

The entry point for background execution begins in `plugins/codex/scripts/codex-companion.mjs`. When a user runs `codex-companion.mjs task … --background`, the `handleTask` function inspects the command options to determine execution mode.

According to the source code at **lines 88‑93**, the function checks `options.background` and immediately delegates to `enqueueBackgroundTask` when the flag is present:

```javascript
// handleTask detects background flag and delegates
if (options.background) {
  return enqueueBackgroundTask(cwd, job, request);
}

```

This decision point separates foreground execution from background processing, ensuring that the latter follows a distinct lifecycle involving process detachment and state persistence.

## Preparing the Job and Payload

Before spawning the detached worker, the plugin constructs a comprehensive `Job` object containing metadata, a unique ID, and log file paths. The system also preserves the original request payload that would execute during a foreground run.

This preparation phase occurs within `enqueueBackgroundTask` and establishes the contract between the parent CLI and the soon-to-be-detached child process. The job structure includes the working directory, task specifications, and file system paths necessary for the worker to reconstruct the execution context independently.

## Spawning the Detached Process

The core of the background task spawning mechanism resides in `spawnDetachedTaskWorker`, which creates a fully independent Node.js process using `child_process.spawn`.

### The spawnDetachedTaskWorker Implementation

As implemented at **lines 71‑80** in `codex-companion.mjs`, the function initiates the worker with specific arguments that place it into "task-worker" mode:

```javascript
function spawnDetachedTaskWorker(cwd, jobId) {
  const scriptPath = path.join(ROOT_DIR, "scripts", "codex-companion.mjs");
  const child = spawn(process.execPath, [
    scriptPath,
    "task-worker",
    "--cwd", cwd,
    "--job-id", jobId
  ], {
    cwd,
    env: process.env,
    detached: true,          // <‑‑ new process group
    stdio: "ignore",        // no attached stdio
    windowsHide: true
  });
  child.unref();            // parent does not wait for child
  return child;
}

```

### Process Configuration Details

The **detached process** configuration utilizes three critical mechanisms to ensure independence:

- **`detached: true`** – Creates a new process group, preventing the child from receiving SIGHUP signals when the parent terminal closes
- **`stdio: "ignore"`** – Disconnects standard input/output streams, allowing the parent to exit without maintaining pipe references
- **`child.unref()`** – Explicitly informs Node.js that the parent event loop should not wait for the child process to exit

This combination ensures that the background task survives even if the user terminates the original `codex-companion` invocation or closes their terminal session.

## State Tracking and Persistence

Immediately after spawning the detached worker, `enqueueBackgroundTask` (lines 84‑100) records the child's PID and job metadata to enable status monitoring:

```javascript
function enqueueBackgroundTask(cwd, job, request) {
  const { logFile } = createTrackedProgress(job);
  appendLogLine(logFile, "Queued for background execution.");

  const child = spawnDetachedTaskWorker(cwd, job.id);
  const queuedRecord = {
    ...job,
    status: "queued",
    phase: "queued",
    pid: child.pid ?? null,
    logFile,
    request
  };
  writeJobFile(job.workspaceRoot, job.id, queuedRecord);
  upsertJob(job.workspaceRoot, queuedRecord);

  return { payload: { jobId: job.id, status: "queued", title: job.title, summary: job.summary, logFile }, logFile };
}

```

The `upsertJob` function updates an in-memory index while `writeJobFile` persists the record to disk. This dual-tracking system allows users to query job status later using `codex-companion.mjs status <job-id>`, even though the original parent process no longer exists.

## Worker Execution Lifecycle

The detached process executes the `handleTaskWorker` function (lines 38‑45), which reconstructs the execution environment from the persisted job file:

```javascript
async function handleTaskWorker(argv) {
  const { options } = parseCommandInput(argv, { valueOptions: ["cwd", "job-id"] });
  const cwd = resolveCommandCwd(options);
  const workspaceRoot = resolveCommandWorkspace(options);
  const storedJob = readStoredJob(workspaceRoot, options["job-id"]);

  const request = storedJob.request;
  const { logFile, progress } = createTrackedProgress({ ...storedJob, workspaceRoot });
  await runTrackedJob(
    { ...storedJob, workspaceRoot, logFile },
    () => executeTaskRun({ ...request, onProgress: progress }),
    { logFile }
  );
}

```

The worker loads the stored request via `readStoredJob`, recreates the tracked progress log, and invokes `executeTaskRun` to perform the actual task logic. This architecture decouples task execution from the CLI lifecycle while maintaining full access to the original execution parameters.

## Broker Process Pattern

The same detached process pattern applies to the Codex broker—a long-running background server that manages multiple jobs. In `plugins/codex/scripts/lib/broker-lifecycle.mjs` (lines 64‑68), the `spawnBrokerProcess` function mirrors the task worker approach:

```javascript
const child = spawn(process.execPath, [scriptPath, "broker"], {
  detached: true,
  stdio: "ignore"
});
child.unref();

```

This consistency across the codebase ensures that both individual background tasks and the persistent broker service operate with the same independence guarantees, surviving parent process termination and continuing until completion or explicit shutdown.

## Summary

- **The Codex plugin** uses `child_process.spawn` with `detached: true` to create independent process groups for background tasks.
- **The `spawnDetachedTaskWorker` function** in `codex-companion.mjs` (lines 71‑80) launches the same script in "task-worker" mode with `stdio: "ignore"` and `child.unref()` to prevent parent-child coupling.
- **State persistence** via `writeJobFile` and `upsertJob` stores the child PID and log paths, enabling status queries after parent exit.
- **The worker process** reconstructs execution context from disk storage using `readStoredJob` and runs the actual task via `executeTaskRun`.
- **The broker lifecycle** (`broker-lifecycle.mjs`) applies identical detachment patterns for long-running server processes.

## Frequently Asked Questions

### What happens to a background task if I close the terminal?

The task continues running because the **detached process** creates a new process group via `detached: true`, isolating it from SIGHUP signals sent to the parent terminal session. The `child.unref()` call ensures Node.js does not keep the parent process alive waiting for the child, allowing the parent to exit completely while the background worker persists.

### Where does the background task store its output and status?

The task writes to a dedicated log file created by `createTrackedProgress` and persists job metadata—including PID, status, and request payload—to a JSON job file in the workspace root via `writeJobFile`. You can query this state later using `codex-companion.mjs status <job-id>` to check progress or retrieve the log path.

### Why does the plugin spawn the same script in "task-worker" mode instead of a separate file?

Using the same entry point (`codex-companion.mjs`) with a "task-worker" argument ensures **code consistency** and **shared dependencies**. The worker reuses the same module resolution, environment configuration, and utility functions as the main CLI, reducing duplication while allowing the process to specialize via command-line routing in `handleTaskWorker`.

### How does the background task mechanism differ from the broker process?

Both use identical Node.js **detached process** primitives (`detached: true`, `stdio: "ignore"`, `unref()`), but **background tasks** are transient workers for single job execution while the **broker** is a persistent server handling multiple jobs. The broker implementation in `broker-lifecycle.mjs` (lines 64‑68) respawns the same script with a "broker" argument rather than "task-worker", creating a long-lived service rather than a single-task process.