# How Background Jobs Are Managed in the OpenAI Codex Plugin: A Complete Technical Guide

> Discover how the OpenAI Codex plugin manages background jobs using a file-based system. Learn about state storage, execution, and querying in this technical guide.

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

---

**The Codex plugin manages background jobs through a lightweight, file-based tracking system that persists metadata, progress, and output to JSON files in the OS temporary directory, using three core modules to handle state storage, job execution, and status querying.**

The `openai/codex-plugin-cc` repository implements a robust background job management subsystem that enables long-running tasks like code reviews to execute asynchronously without blocking the IDE. This system relies on a durable file-based architecture rather than in-memory storage, ensuring job data survives process restarts while maintaining a strict separation between runtime state and source control.

## Core Architecture and State Storage

The foundation of the job management system resides in **`plugins/codex/scripts/lib/state.mjs`**, which manages persistence through a dedicated state directory located at **`$TMPDIR/codex-companion/state/`**. This module maintains a global **[`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json)** file alongside individual JSON files for each job, providing functions like **`resolveStateDir`** to locate the storage path, **`loadState`** to hydrate the global state, and **`saveState`** to persist changes while automatically pruning old records.

Each job receives a unique identifier via **`generateJobId`**, and the system records metadata including timestamps, current status, and optional session identifiers through **`upsertJob`** and **`writeJobFile`**. The **`readJobFile`** function enables retrieval of specific job records, creating a reliable interface for background task metadata that remains accessible across process lifetimes.

## Job Lifecycle and Execution Management

The **`plugins/codex/scripts/lib/tracked-jobs.mjs`** module orchestrates the actual execution flow through the **`runTrackedJob`** function, which serves as a wrapper for user-supplied task runners. When initiating work, the system calls **`createJobRecord`** to initialize metadata with `createdAt` timestamps and session scoping, then invokes **`createJobLogFile`** to establish a plain-text log file named `<jobId>.log` for human-readable output.

The lifecycle proceeds through distinct phases:

1. **Initialization** – Writes a "running" status entry with `pid` and `startedAt` timestamp via **`upsertJob`**.
2. **Execution** – Invokes the runner function passed to `runTrackedJob`.
3. **Completion** – Updates the job record with final status (`completed` or `failed`), `completedAt` timestamp, result payload, and rendered output.

This ensures every job transition persists to disk immediately, preventing data loss during unexpected terminations.

## Progress Tracking and Log Management

During execution, tasks report progress through the **`createProgressReporter`** helper, which returns a reporter function that normalizes progress events and writes structured lines to the job's log file via **`appendLogLine`** or **`appendLogBlock`**. These helpers write to the dedicated log file while optionally echoing progress to `stderr`, enabling real-time monitoring without corrupting the JSON state files.

The separation between machine-readable state (JSON) and human-readable logs (plain text) allows the system to maintain structured data for programmatic queries while preserving detailed execution traces for debugging purposes.

## Querying Jobs and Session Isolation

For UI commands like `/codex:status` and `/codex:cancel`, the **`plugins/codex/scripts/lib/job-control.mjs`** module provides high-level aggregation through **`buildStatusSnapshot`** and **`buildSingleJobSnapshot`**. These functions enumerate stored job files, categorize them as running, recent, or finished, and enrich each record with elapsed time calculations and phase inference via **`inferLegacyJobPhase`**.

The system supports multi-session isolation through the **`CODEX_COMPANION_SESSION_ID`** environment variable. Functions like **`filterJobsForCurrentSession`** and **`getCurrentSessionId`** ensure that status queries only return jobs belonging to the current session, preventing cross-contamination between different IDE instances.

Maintenance occurs automatically through **`saveState`** in `state.mjs`, which retains only the newest **MAX_JOBS** (50) entries and removes older files to prevent unbounded disk usage.

## Implementation Examples

The following examples demonstrate how to interact with the background job system:

```javascript
// Start a tracked job (e.g., a review task)
import { generateJobId, createJobRecord, createJobLogFile, runTrackedJob } from "./tracked-jobs.mjs";

async function startReviewJob(workspaceRoot, reviewTask) {
  const jobId = generateJobId("review");
  const logFile = createJobLogFile(workspaceRoot, jobId, "Codex Review");
  const job = createJobRecord(
    {
      id: jobId,
      workspaceRoot,
      jobClass: "review",
      kind: "review",
      logFile,
    },
    {}
  );

  // The runner does the actual work and returns an execution object
  const runner = async () => {
    // …perform review, return { exitStatus, payload, rendered, threadId, turnId, summary }
  };

  return runTrackedJob(job, runner, { logFile });
}

```

```javascript
// Report progress from inside the runner
import { createProgressReporter } from "./tracked-jobs.mjs";

const reporter = createProgressReporter({ stderr: true, logFile });
reporter({ message: "Searching repository...", phase: "investigating" });

```

```javascript
// Retrieve a concise status snapshot for the current session
import { buildStatusSnapshot } from "./job-control.mjs";

const snapshot = buildStatusSnapshot(process.cwd(), { env: process.env });
console.log("Running jobs:", snapshot.running);
console.log("Recent jobs:", snapshot.recent);

```

```javascript
// Cancel an active job by ID
import { resolveCancelableJob } from "./job-control.mjs";

async function cancelJob(jobId) {
  const { workspaceRoot, job } = resolveCancelableJob(process.cwd(), jobId);
  // Here you could send a SIGTERM to job.pid or simply mark it cancelled
  // For demonstration, we just update the state:
  const { upsertJob } = await import("./state.mjs");
  upsertJob(workspaceRoot, { id: job.id, status: "cancelled", completedAt: new Date().toISOString() });
}

```

## Summary

- **File-based persistence** stores all job data in `$TMPDIR/codex-companion/state/`, separating runtime state from source control.
- **Three-module architecture** divides responsibilities between `state.mjs` (persistence), `tracked-jobs.mjs` (execution), and `job-control.mjs` (querying).
- **Complete lifecycle management** occurs through the `runTrackedJob` wrapper, which handles status transitions from "running" to "completed" or "failed".
- **Real-time progress reporting** uses `createProgressReporter` to write structured updates to dedicated log files.
- **Session isolation** filters jobs by `CODEX_COMPANION_SESSION_ID` to prevent cross-session leakage.
- **Automatic pruning** limits retention to 50 jobs via `MAX_JOBS`, preventing unbounded disk usage.

## Frequently Asked Questions

### Where does the Codex plugin store background job data?

According to the `openai/codex-plugin-cc` source code, all background job data resides in the OS temporary directory at `$TMPDIR/codex-companion/state/`. This location contains a global [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) file plus individual JSON files for each job and plain-text `.log` files for human-readable output, ensuring data persists across process restarts while remaining outside the repository.

### How does `runTrackedJob` handle job completion and failures?

The `runTrackedJob` function in `tracked-jobs.mjs` wraps the user-supplied runner and automatically manages state transitions. Upon the runner's resolution, it updates the job record via `upsertJob` with the final status (`completed` or `failed`), timestamps (`completedAt`), the result payload, and rendered output. It then appends the final output to the job's log file, ensuring atomic persistence of the terminal state.

### What is the maximum number of jobs retained by the state manager?

The `saveState` function in `state.mjs` enforces a hard limit of **50 jobs** defined by the `MAX_JOBS` constant. When persisting state, the system keeps only the newest 50 entries and automatically deletes older job files and their associated logs, preventing unbounded growth in the temporary directory.

### How does session scoping isolate jobs between different Codex companion sessions?

The system respects the `CODEX_COMPANION_SESSION_ID` environment variable to isolate jobs. Functions like `filterJobsForCurrentSession` and `getCurrentSessionId` in `job-control.mjs` filter the global job list to return only those records matching the current session ID. This ensures that `/codex:status` and other UI commands only display jobs initiated by the current IDE instance, preventing cross-contamination between multiple simultaneous sessions.