# How the Codex Plugin Tracks and Reports Job Progress in Real-Time

> Discover how the Codex plugin tracks and reports job progress in real-time. Learn about its efficient JSON record persistence and in-memory state index for instant updates.

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

---

**The Codex plugin maintains live visibility of job execution by persisting lightweight JSON records to disk and maintaining an in-memory state index that external tools can poll for real-time updates.**

The `openai/codex-plugin-cc` repository implements a robust job tracking system that enables real-time monitoring of long-running Codex operations. By combining atomic file writes with a normalized progress event schema, the plugin ensures that both the local process and external observers can accurately track job phases without requiring persistent network connections.

## Core Job Tracking Architecture

The tracking system centers on three primitives defined in `plugins/codex/scripts/lib/tracked-jobs.mjs`: **job records**, **progress updaters**, and **progress reporters**. Together, these components form a pipeline that captures state changes from initiation through completion.

### Job Record Creation

When a job starts, the `runTrackedJob` function (lines 42-50) initializes a new job record by assigning a unique ID via `generateJobId` and writing an initial state object with `status: "running"` and a `createdAt` timestamp. This record serves as the single source of truth for the job's lifecycle.

### State Persistence Layer

All job data persists under a workspace-specific directory resolved by `resolveStateDir`. The plugin maintains two distinct storage mechanisms:

- **[`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json)** – A global index containing the 50 most recent jobs, enabling fast lookups of active and historical operations.
- **`<jobId>.json`** – Individual mutable records for each active job, storing the current phase, thread ID, turn ID, and metadata.

The `upsertJob` function in `plugins/codex/scripts/lib/state.mjs` (lines 29-31) handles atomic updates to both locations, ensuring that in-memory indices remain synchronized with disk state.

## Real-Time Progress Updates

The plugin bridges execution and reporting through the `createJobProgressUpdater` factory (lines 70-84 in `tracked-jobs.mjs`). This updater normalizes incoming events—extracting fields like `phase`, `threadId`, and `turnId`—and persists changes only when actual state transitions occur, minimizing unnecessary I/O.

### Incremental State Patching

Rather than rewriting entire records, the updater patches specific fields using `upsertJob`. This approach supports high-frequency updates during intensive operations like compilation or testing while maintaining file integrity.

## Reporting Progress to Users

While the updater manages machine-readable state, the `createProgressReporter` function (lines 17-33 and 36-44) handles human-readable output. The factory accepts configuration for `stderr` streaming, log file appending via `appendLogLine` and `appendLogBlock`, or custom event handlers.

### Dual-Channel Output

A typical configuration pipes messages to both the terminal and a dedicated log file (`<jobId>.log`), ensuring that real-time status remains visible in the console while creating a persistent audit trail on disk.

## Completion and Error Handling

When execution finishes, `runTrackedJob` (lines 54-71) finalizes the record by setting `status` to `"completed"` and populating `result`, `rendered`, and `summary` fields. If the runner throws an exception, the catch block (lines 81-89) captures the error message and transitions the status to `"failed"`, preserving diagnostic information for post-mortem analysis.

## Complete Implementation Example

The following example demonstrates initializing a tracked job, configuring progress reporting, and executing work with real-time state updates:

```javascript
import { generateJobId, createJobRecord, createJobProgressUpdater, createProgressReporter, runTrackedJob } from "./tracked-jobs.mjs";
import { resolveWorkspaceRoot } from "./workspace.mjs";

// Initialize job context
const workspaceRoot = resolveWorkspaceRoot(process.cwd());
const jobId = generateJobId("example");
const job = createJobRecord(
  { id: jobId, workspaceRoot, title: "Example Job", logFile: null },
  { env: process.env }
);

// Configure reporting to stderr and log file
const logFile = createJobLogFile(workspaceRoot, jobId, "Example Job");
const reporter = createProgressReporter({ 
  stderr: true, 
  logFile, 
  onEvent: (e) => console.log("🔸", e) 
});

// Create updater for persisting state changes
const progressUpdater = createJobProgressUpdater(workspaceRoot, jobId);

// Execute tracked work
await runTrackedJob(job, async () => {
  reporter({ message: "Initializing…" });
  await new Promise(r => setTimeout(r, 500));

  progressUpdater({ phase: "compile", message: "Compiling sources…" });
  await new Promise(r => setTimeout(r, 800));

  progressUpdater({ phase: "test", message: "Running tests…" });
  await new Promise(r => setTimeout(r, 600));

  return {
    exitStatus: 0,
    payload: { success: true },
    rendered: "All steps succeeded",
    summary: "Job completed"
  };
}, { logFile });

```

In this implementation:

- `reporter` streams human-readable messages to `stderr` and the log file.
- `progressUpdater` persists structural state changes to JSON records.
- `runTrackedJob` manages the job lifecycle and finalizes the record upon completion.

## Summary

- **File-based persistence**: The Codex plugin stores job state in [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) (global index) and individual `<jobId>.json` files, enabling external tools to poll for updates without network sockets.
- **Normalized progress events**: The `createJobProgressUpdater` function extracts `phase`, `threadId`, and `turnId` from events, updating disk records only when values change.
- **Dual reporting channels**: `createProgressReporter` supports simultaneous output to `stderr`, log files, and custom handlers.
- **Lifecycle management**: `runTrackedJob` in `tracked-jobs.mjs` handles initialization, status transitions, and error capture, setting `status` to `"completed"` or `"failed"` based on execution results.
- **Workspace isolation**: State directories are resolved per-workspace via `resolveStateDir`, preventing job ID collisions across different projects.

## Frequently Asked Questions

### How does the Codex plugin store job progress data?

The plugin writes to three file types in a workspace-specific directory: [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) maintains a rolling index of the latest 50 jobs, `<jobId>.json` stores the mutable record for an individual job, and `<jobId>.log` contains the human-readable message stream. This file-based approach allows external processes to read real-time status without maintaining active connections.

### What function creates the progress updater in the Codex plugin?

The `createJobProgressUpdater` function (lines 70-84 in `plugins/codex/scripts/lib/tracked-jobs.mjs`) returns a callback that normalizes progress events and persists them via `upsertJob`. It compares incoming fields against the current state and writes updates only when `phase`, `threadId`, `turnId`, or other tracked properties change.

### How does the plugin handle job failures?

When an exception occurs within `runTrackedJob`, the catch block (lines 81-89) captures the error message, writes it to the job record's `errorMessage` field, and sets the `status` to `"failed"`. This ensures that incomplete jobs are clearly marked and diagnostic information is preserved in the JSON record for inspection by the Codex UI or CLI tools.

### Can external tools monitor job progress without using the plugin's API?

Yes. Because the plugin persists all state to JSON files on disk, any external tool—including the Codex CLI (`codex status`) or custom scripts—can read the [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) index or individual `<jobId>.json` files to determine current status, phase, and completion percentage. This design decouples the monitoring interface from the execution runtime.