How Background Job Execution Works in the Codex Plugin: A Deep Dive into the OpenAI Codex Companion

The Codex plugin implements background job execution by spawning detached Node.js child processes that persist job state to JSON files and stream progress to dedicated log files, allowing long-running tasks to survive after the parent CLI exits.

The openai/codex-plugin-cc repository provides a robust asynchronous job system for long-running tasks such as code reviews and rescue operations. When users invoke the Codex companion with the --background flag, the system transitions from synchronous execution to a persistent background model that tracks progress via JSON state files and per-job logs. This architecture ensures that expensive operations continue even after the terminal session ends or the parent process exits.

Core Architecture of Background Job Execution

The background job system spans four primary modules that coordinate state persistence, process management, and progress tracking:

  1. state.mjs – Handles JSON-based persistence of job metadata in the user’s temporary directory.
  2. tracked-jobs.mjs – Manages log file creation, job record construction, and the execution wrapper runTrackedJob.
  3. codex-companion.mjs – Entry point that enqueues tasks and spawns detached workers via enqueueBackgroundTask.
  4. job-control.mjs – Provides status snapshots and phase inference for the /codex:status command.

Job State Management and Persistence

State Storage in state.mjs

All background job metadata lives in a JSON-based state directory under the user’s temporary folder. The state.mjs module exports utilities to load, save, prune, and upsert job records. When a job transitions from foreground to background, the system writes a structured record containing the job ID, status, timestamps, PID, and log file path.

Tracking Progress with tracked-jobs.mjs

The tracked-jobs.mjs file defines createTrackedProgress, which initializes a unique log file for each job, and writeJobFile, which serializes the job record to disk. These functions ensure that both the worker process and subsequent CLI invocations can locate and update the same job state.

Enqueueing and Spawning Detached Workers

The enqueueBackgroundTask Flow

Located in codex-companion.mjs, the enqueueBackgroundTask function orchestrates the transition from foreground to background. It performs the following steps:

  • Calls createTrackedProgress to reserve a log file path.
  • Writes "Queued for background execution." to the log via appendLogLine.
  • Invokes spawnDetachedTaskWorker to fork a new Node process.
  • Persists a "queued" job record using writeJobFile and upsertJob, capturing the child PID.

Process Detachment with spawnDetachedTaskWorker

The spawnDetachedTaskWorker function constructs a detached child process using Node’s spawn with specific options that ensure survival after parent exit:

import { spawn } from "child_process";
import path from "path";

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;
}

The detached: true and stdio: "ignore" options allow the parent to exit immediately while the worker continues execution. The windowsHide flag ensures clean operation on Windows systems.

The Detached Worker Lifecycle

Running Jobs with runTrackedJob

Once detached, the child process runs codex-companion.mjs with the task-worker sub-command. It invokes runTrackedJob from tracked-jobs.mjs, which executes the actual command and manages the job lifecycle:

import { runTrackedJob } from "./tracked-jobs.mjs";

async function workerMain(job, runner) {
  // runner receives a progress reporter bound to the job’s log file
  const execResult = await runTrackedJob(job, () => runner(progress), { logFile: job.logFile });
  // execResult contains { exitStatus, payload, rendered, ... }
}

Log Appending and Status Updates

During execution, the worker updates the job record via appendLogLine and appendLogBlock. When the runner finishes, runTrackedJob atomically updates the status to completed or failed, records timestamps, captures output, and clears the PID field.

Querying Job Status and Results

The job-control.mjs module provides the enrichJob function, which reads the per-job log preview and infers the current phase (e.g., "queued", "running", "completed"). This powers the /codex:status command, allowing users to query elapsed times and current states even for jobs started in previous terminal sessions.

Summary

  • Persistence: Job state is stored as JSON in the user’s temporary directory via state.mjs, ensuring data survives process restarts.
  • Detachment: Background jobs are spawned using spawnDetachedTaskWorker with detached: true and stdio: "ignore" to isolate them from the parent process.
  • Tracking: tracked-jobs.mjs provides createTrackedProgress and runTrackedJob to manage log files and lifecycle updates.
  • Enqueueing: The enqueueBackgroundTask function in codex-companion.mjs coordinates the handoff from foreground to background.
  • Monitoring: job-control.mjs enables status queries and progress previews through the enrichJob helper.

Frequently Asked Questions

How does the Codex plugin detach a background job from the parent process?

The plugin uses Node.js’s child_process.spawn with the detached: true option in spawnDetachedTaskWorker (located in codex-companion.mjs). This creates a new process group independent of the parent, while stdio: "ignore" and child.unref() ensure the parent can exit without waiting for the child to finish.

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. Per-job logs are created by createTrackedProgress in tracked-jobs.mjs and updated via appendLogLine during execution.

Can I check the status of a background job after the CLI exits?

Yes. Because job state is written to disk, the job-control.mjs module can read the same JSON records and log files in subsequent CLI invocations. The /codex:status command uses enrichJob to display current phase, elapsed time, and log previews.

How does the plugin handle job completion or failure in detached mode?

The runTrackedJob function in tracked-jobs.mjs captures the exit status of the underlying command and updates the job record to completed or failed. It records timestamps, persists final output, and clears the PID field, allowing job-control.mjs to report the final state accurately.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →