# Process Lifecycle for Background Codex Jobs in the OpenAI Codex Plugin

> Understand the background Codex jobs lifecycle: enqueue, start, monitor, complete, retrieve, and cancel. Learn how state is managed in state.json and Node.js workers.

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

---

**The process lifecycle for background Codex jobs follows six distinct stages—enqueue, start, monitor, complete, retrieve, and cancel—managed through a persistent state registry in [`.codex/state.json`](https://github.com/openai/codex-plugin-cc/blob/main/.codex/state.json) and detached Node.js workers that survive Claude Code session restarts.**

The OpenAI Codex plugin for Claude Code enables developers to run resource-intensive operations (review, adversarial-review, task, and rescue commands) asynchronously using the `--background` flag. Understanding the process lifecycle for background Codex jobs is essential for monitoring long-running operations, retrieving results after session disconnections, and gracefully managing cancellations across POSIX and Windows platforms.

## Stage 1: Enqueue and Job Registration

When a user invokes a Codex command with `--background`, the companion script `codex-companion.mjs` initiates the lifecycle by generating a unique identifier and persisting the job metadata.

The script first creates a unique `jobId` (e.g., `task-abc123`) via `generateJobId()`, then builds a payload containing the command type, arguments, and creation timestamp. This record is stored in the **tracked-jobs** registry via `trackedJobs.enqueue()` in `tracked-jobs.mjs`, which writes to the persistent state file at [`.codex/state.json`](https://github.com/openai/codex-plugin-cc/blob/main/.codex/state.json) inside the repository root.

Immediately after enqueueing, the script spawns a **detached Node worker** using `spawnDetachedWorker()`, which launches `node scripts/codex-companion.mjs` as an independent process. This detachment ensures the job continues executing even if the original Claude Code session terminates.

```javascript
// plugins/codex/scripts/codex-companion.mjs
const jobId = generateJobId();               // e.g. `task-abc123`
await trackedJobs.enqueue({
  id: jobId,
  command: "review",
  args,
  status: "queued",
  createdAt: Date.now(),
});
spawnDetachedWorker(jobId, args);            // Detached worker process

```

## Stage 2: Execution and Process Initialization

Once the detached worker starts, it transitions the job from **queued** to **running** by updating the state registry via `state.updateJob()` in `state.mjs`. The worker records its own process ID (PID) to enable later monitoring and cancellation.

The actual Codex CLI execution is handled by `runCommandChecked()` in `process.mjs`, which invokes the `codex` binary with the appropriate arguments and repository root context. The CLI writes logs to its own streams and ultimately produces a JSON-encoded result file upon completion.

```javascript
// Inside the detached worker (process.mjs)
const result = runCommandChecked("codex", codexArgs, { cwd: repoRoot });
await state.updateJob(jobId, { status: "running", pid: process.pid });

```

## Stage 3: Monitoring and Status Queries

While the job executes, users can query its progress using the `/codex:status` command. This command reads the tracked-jobs registry via `state.getJob()` and determines the current state by checking whether the stored PID is still alive using `isProcessAlive()`.

The system reports one of five statuses: **queued**, **running**, **succeeded**, **failed**, or **cancelled**. Because the registry persists to disk, status queries work even if the original Claude Code process has restarted or the user has opened a new session.

```javascript
// plugins/codex/scripts/lib/state.mjs (status command)
const job = await state.getJob(jobId);
if (job.pid && isProcessAlive(job.pid)) {
  return `Job ${jobId} is running (PID ${job.pid})`;
}
return `Job ${jobId} is ${job.status}`;

```

## Stage 4: Completion and Result Persistence

When the Codex CLI exits, the worker captures the exit status and updates the job record accordingly. An exit code of 0 marks the job as **succeeded**, while any non-zero status marks it as **failed**.

The final update stores the stdout and stderr output, completion timestamp, and any generated `codex-resume` token or session ID. This data is written to the state registry via `state.updateJob()`, ensuring the results survive worker process termination.

```javascript
// Worker completion handling (process.mjs + state.mjs)
await state.updateJob(jobId, {
  status: result.status === 0 ? "succeeded" : "failed",
  finishedAt: Date.now(),
  output: result.stdout,
  error: result.stderr,
});

```

## Stage 5: Result Retrieval

After a job completes, users retrieve output using the `/codex:result` command. This reads the stored job record from `state.mjs` and formats the output for display. If the job succeeded, the output includes the Codex CLI's response and may contain a `codex resume <session-id>` command that allows users to reopen the exact Codex session in a new interactive context.

```javascript
// Result command implementation (state.mjs)
const job = await state.getJob(jobId);
if (job.status === "succeeded") {
  console.log(job.output);                  // Codex's final output
}

```

## Stage 6: Cancellation and Process Termination

Users can abort running jobs using `/codex:cancel <jobId>`. This invokes `terminateProcessTree()` from `process.mjs`, which handles platform-specific process cleanup.

On **Windows**, the plugin executes `taskkill /PID <pid> /T /F` to forcibly terminate the process tree. On **POSIX** platforms (Linux/macOS), it sends `SIGTERM` to the entire process group using `kill -PID`. If the process has already exited, the termination routine reports `delivered = false` but still updates the job status to **cancelled** via `state.updateJob()`.

```javascript
// Cancellation handling (process.mjs)
const { attempted, delivered, method } = terminateProcessTree(job.pid);
await state.updateJob(jobId, { status: "cancelled", cancelledAt: Date.now() });

```

## Key Architectural Components

The background job lifecycle relies on five core modules within `plugins/codex/scripts/`:

- **`codex-companion.mjs`** – Entry point that parses user commands, generates `jobId` values, and spawns detached workers.
- **`lib/tracked-jobs.mjs`** – Registry abstraction for enqueueing, updating, and listing jobs in the persistent store.
- **`lib/state.mjs`** – Persistence layer managing [`.codex/state.json`](https://github.com/openai/codex-plugin-cc/blob/main/.codex/state.json) and providing status/result query helpers.
- **`lib/process.mjs`** – Low-level utilities for executing external commands (`runCommandChecked`) and safely terminating process trees (`terminateProcessTree`).
- **`lib/job-control.mjs`** – High-level orchestration coordinating worker spawning and cancellation workflows.

## Summary

- **Enqueue**: `codex-companion.mjs` generates a unique `jobId`, persists the job to `tracked-jobs.mjs`, and spawns a detached Node worker.
- **Start**: The worker updates the status to **running**, records its PID, and executes the Codex CLI via `process.mjs`.
- **Monitor**: `/codex:status` queries [`.codex/state.json`](https://github.com/openai/codex-plugin-cc/blob/main/.codex/state.json) and checks PID liveness to report queued, running, succeeded, failed, or cancelled states.
- **Complete**: On CLI exit, the worker stores stdout/stderr and marks the job **succeeded** or **failed** based on the exit code.
- **Retrieve**: `/codex:result` fetches persisted output from `state.mjs`, including optional resume tokens for session restoration.
- **Cancel**: `/codex:cancel` triggers `terminateProcessTree()` (Windows `taskkill` or POSIX `SIGTERM`) and updates the status to **cancelled**.

## Frequently Asked Questions

### How does the Codex plugin maintain job state across Claude Code restarts?

The plugin persists all job metadata, status, and results to [`.codex/state.json`](https://github.com/openai/codex-plugin-cc/blob/main/.codex/state.json) in the repository root via `state.mjs` and `tracked-jobs.mjs`. Because background jobs run as detached OS processes independent of the Claude Code session, the state file survives process restarts, enabling status queries and result retrieval in new sessions.

### What happens when a background job is invoked with `--wait` instead of `--background`?

While the raw analysis focuses on `--background`, the plugin architecture supports both flags. The `--wait` flag typically blocks the Claude Code session until completion, whereas `--background` immediately detaches the worker process. Both modes use the same underlying lifecycle stages and state persistence mechanisms, but `--background` returns control to the user immediately after the enqueue stage.

### How does cross-platform process termination differ in the Codex plugin?

According to `process.mjs`, the `terminateProcessTree()` function implements platform-specific logic: on Windows it spawns `taskkill /PID <pid> /T /F` to forcibly kill the process tree, while on POSIX systems it sends `SIGTERM` to the negative PID (process group) to gracefully terminate the worker and any child processes. Both methods ensure the job record updates to **cancelled** even if the process has already exited.

### Can I resume an interactive Codex session from a completed background job?

Yes. When a background job succeeds, the result data stored in `state.mjs` includes a session ID and generates a `codex resume <session-id>` command in the output. Running this command in a new Claude Code session restores the exact Codex context, allowing you to continue work from where the background operation left off.