# How Background Job Execution Is Implemented in the Codex Plugin

> Learn how the Codex plugin handles background job execution by persisting job data, spawning detached Node.js processes, and using a dedicated worker subcommand for async task completion.

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

---

**The Codex plugin implements background job execution by persisting job metadata to JSON state files, spawning detached Node.js child processes via `spawnDetachedTaskWorker`, and using a dedicated worker subcommand to run tasks asynchronously while allowing parent processes to exit immediately.**

The openai/codex-plugin-cc repository provides a robust asynchronous task system for long-running Codex operations like reviews and rescues. When users pass the `--background` flag, the system switches from synchronous execution to a detached worker model that persists state and allows status monitoring. Understanding this implementation reveals how the plugin isolates long-running work while maintaining observability through persistent logs and JSON state files.

## Job State Management Architecture

Background jobs in the Codex plugin rely on a JSON-based state directory stored under the user’s temporary folder. The **`state.mjs`** module handles loading, saving, pruning, and upserting job records, while **`tracked-jobs.mjs`** manages log file creation and progress updates.

When a background task is initiated, the system creates a persistent job record containing the status, timestamps, PID, and log file path. This record is written to the state directory via `writeJobFile` and synchronized with the global job list through `upsertJob`. The separation between file operations and state management allows the plugin to maintain consistency even when the parent process exits.

## Enqueueing Background Tasks

The entry point for background execution is the **`enqueueBackgroundTask`** function (lines 84-99) defined in `plugins/codex/scripts/codex-companion.mjs`. This function orchestrates the transition from foreground to background execution through four distinct steps:

1. **Initialize logging**: Calls `createTrackedProgress` to generate a unique log file for the job.
2. **Queue notification**: Writes "Queued for background execution." to the log via `appendLogLine`.
3. **Spawn worker**: Invokes `spawnDetachedTaskWorker` to create a detached Node.js child process.
4. **Persist state**: Saves the job record with `status: "queued"` and the child PID via `writeJobFile` and `upsertJob`.

```javascript
// Simplified enqueue flow from codex-companion.mjs
import { createTrackedProgress, appendLogLine, writeJobFile, upsertJob } from "./lib/tracked-jobs.mjs";
import { spawnDetachedTaskWorker } from "./codex-companion.mjs";

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

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

```

## Spawning Detached Workers

The **`spawnDetachedTaskWorker`** function (lines 71-80) in `codex-companion.mjs` creates a platform-agnostic detached process that survives parent termination. This function launches the same script with the `task-worker` subcommand, passing the job ID and working directory as arguments.

Critical to the implementation are the `detached: true` and `stdio: "ignore"` options, which ensure the child process runs independently of the parent’s I/O streams. The `child.unref()` call allows the parent event loop to exit while the worker continues execution.

```javascript
// Platform-agnostic detached worker spawning
function spawnDetachedTaskWorker(cwd, jobId) {
  const script = path.join(ROOT_DIR, "scripts", "codex-companion.mjs");
  const child = spawn(process.execPath, [
    script, 
    "task-worker", 
    "--cwd", cwd, 
    "--job-id", jobId
  ], {
    cwd,
    env: process.env,
    detached: true,
    stdio: "ignore",
    windowsHide: true,
  });
  child.unref();
  return child;
}

```

## Worker Execution and Lifecycle

Once spawned, the detached worker executes the **`runTrackedJob`** function (lines 42-104) from `plugins/codex/scripts/lib/tracked-jobs.mjs`. This function wraps the actual task execution with progress tracking and state management, utilizing **`process.mjs`** for command spawning.

The worker receives a progress reporter bound to the job’s log file, allowing real-time updates via `appendLogLine` and `appendLogBlock`. Upon completion, `runTrackedJob` atomically updates the job status to `completed` or `failed`, records timestamps and output, and clears the PID field to indicate the process has finished.

```javascript
// Worker execution flow
import { runTrackedJob } from "./lib/tracked-jobs.mjs";

async function workerMain(job, runner) {
  // Runner receives progress reporter bound to job.logFile
  const execResult = await runTrackedJob(
    job, 
    () => runner(progress), 
    { logFile: job.logFile }
  );
  // execResult contains exitStatus, payload, rendered output
}

```

## Monitoring and Control

While jobs run in the background, users query status through the **`job-control.mjs`** module. The **`enrichJob`** function (lines 61-80) reads the persistent log file to generate progress previews and infers the current phase from the job state.

This architecture enables the `/codex:status` command to display accurate elapsed times, current phases, and log snippets without requiring an active connection to the worker process. The state files serve as the single source of truth, allowing external processes to monitor, cancel, or retrieve results from completed jobs.

## Summary

The Codex plugin’s background job execution combines persistent JSON state management with detached process spawning to create a robust asynchronous task system:

- **`state.mjs`** provides atomic job persistence operations (load, save, upsert) in the user’s temp directory.
- **`enqueueBackgroundTask`** transitions jobs from foreground to background by spawning detached workers and writing queued state.
- **`spawnDetachedTaskWorker`** creates platform-independent detached processes using `detached: true` and `stdio: "ignore"`.
- **`runTrackedJob`** executes the actual work while maintaining per-job log files and updating status atomically.
- **`job-control.mjs`** enables external monitoring through log file parsing and state enrichment.

## Frequently Asked Questions

### How does the Codex plugin ensure background jobs survive after the parent process exits?

The plugin uses Node.js `child_process.spawn` with the `detached: true` option and `stdio: "ignore"` configuration in `spawnDetachedTaskWorker`. This combination creates a fully independent process group, and the subsequent `child.unref()` call allows the parent’s event loop to terminate while the worker continues executing. The worker process re-runs the same script with the `task-worker` subcommand to handle the actual job execution.

### Where does the Codex plugin store background job state and logs?

Job metadata is persisted as JSON files in a state directory under the user’s temporary folder, managed by `state.mjs`. Each job also receives a dedicated log file created by `createTrackedProgress` in `tracked-jobs.mjs`. These paths are stored in the job record, allowing the `job-control.mjs` module to read progress and status even when the original process no longer exists.

### Can background jobs be monitored while running?

Yes. The `job-control.mjs` module provides the `enrichJob` function that reads the persistent log file and infers the current phase from the job state. This allows the `/codex:status` command to display real-time progress, elapsed time, and log previews without requiring an active connection to the worker process. The state files act as the single source of truth for monitoring tools.

### What happens if a background job fails or completes?

When the worker finishes execution, `runTrackedJob` in `tracked-jobs.mjs` updates the job record by setting the status to `completed` or `failed`, recording the final timestamps, capturing the output payload, and clearing the PID field. This atomic update ensures that subsequent status queries immediately reflect the final state, and the cleared PID indicates no active process is running for that job.