# How the Codex Plugin Tracks Job Status and Stores Job Records

> Discover how the Codex plugin tracks job status and stores job records using a filesystem-based system. Learn about real-time updates and atomic file operations for efficient progress tracking.

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

---

**The Codex plugin uses a lightweight filesystem-based state management system that writes JSON job records and plain-text logs to a deterministic per-workspace directory, maintaining a bounded central index and enabling real-time progress updates through atomic file operations.**

The `openai/codex-plugin-cc` repository implements a deterministic, crash-resilient job tracking system designed to persist metadata for asynchronous operations like Codex CLI commands and rescue operations. Understanding how the Codex plugin tracks job status reveals a hybrid architecture combining immutable JSON snapshots, append-only log files, and an in-memory index that automatically prunes older entries.

## Core Architecture: State Directory and Job Records

The foundation of the tracking system rests on three primitives: a deterministic state directory, individual JSON job records, and companion log files.

### Deterministic State Directory Resolution

All job data lives in a per-workspace folder whose path is derived from the workspace root to avoid collisions. The `resolveStateDir` function in **`plugins/codex/scripts/lib/state.mjs`** first checks the `CLAUDE_PLUGIN_DATA` environment variable, then falls back to a temporary path at `/tmp/codex-companion/state` hashed by workspace root.

```javascript
// Simplified logic from state.mjs lines 29-44
const stateDir = resolveStateDir(workspaceRoot);
// Returns: $CLAUDE_PLUGIN_DATA/<hashed-workspace-path> or /tmp/codex-companion/state/...

```

The workspace root itself is determined by `resolveWorkspaceRoot` in **`plugins/codex/scripts/lib/workspace.mjs`** (lines 3-9), which detects Git repositories or falls back to the current working directory.

### JSON Job Records and Log Files

Each job receives two files: a structured metadata file and a human-readable log.

- **Job records**: Stored as `<jobId>.json` containing `status`, `timestamps`, `phase`, `PID`, and optional `threadId`/`turnId`. The `resolveJobFile` and `writeJobFile` utilities in **`state.mjs`** (lines 66-71) handle serialization.
- **Job logs**: Stored as `<jobId>.log` and written via `appendLogLine` and `appendLogBlock` in **`plugins/codex/scripts/lib/tracked-jobs.mjs`** (lines 36-43, 51-49).

## Job Lifecycle Management

The system provides high-level wrappers that orchestrate the entire job lifecycle from creation to completion.

### Creating and Executing Tracked Jobs

The `runTrackedJob` function in **`tracked-jobs.mjs`** (lines 42-54, 55-80) serves as the primary entry point. It performs three atomic steps:

1. **Initialization**: Creates a job record with status `running` using `createJobRecord` and persists it via `upsertJob`.
2. **Execution**: Runs the supplied async runner function.
3. **Finalization**: Updates the record to `completed` or `failed`, writes final timestamps, and stores rendered output.

```javascript
import { generateJobId, createJobRecord } from "./state.mjs";
import { runTrackedJob, createJobLogFile } from "./tracked-jobs.mjs";

const job = createJobRecord({
  id: generateJobId(),
  workspaceRoot: resolveWorkspaceRoot(process.cwd()),
  title: "Long-running analysis",
  logFile: createJobLogFile(workspaceRoot, "analysis", "Analysis Task")
});

// Execute with automatic state management
await runTrackedJob(job, asyncRunner, { logFile: job.logFile });

```

*Key implementation*: Job ID generation and record creation reside in **`state.mjs`** (lines 24-27), while the execution wrapper is implemented in **`tracked-jobs.mjs`** (lines 42-54).

### Real-Time Progress Updates

Long-running jobs stream updates without rewriting the entire state index. The `createJobProgressUpdater` factory in **`tracked-jobs.mjs`** (lines 70-86, 98-114) returns a function that:

1. Normalizes incoming events (detecting changes to `phase`, `threadId`, or `turnId`)
2. Patches the in-memory record
3. Atomically rewrites the specific `<jobId>.json` file via `upsertJob`

```javascript
import { createJobProgressUpdater } from "./tracked-jobs.mjs";

const update = createJobProgressUpdater(workspaceRoot, job.id);

// Emit progress during execution
update({
  phase: "analyzing",
  threadId: "thread-42",
  turnId: "turn-7",
  message: "Scanning codebase..."
});

```

## State Persistence and Pruning

To prevent unbounded growth, the system maintains a central index file called [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) containing only the 50 most recent jobs.

The `saveState` function in **`state.mjs`** (lines 80-95) serializes the trimmed job list and automatically prunes orphaned `.json` and `.log` files that no longer appear in the index. Every mutation flows through `upsertJob`, which updates the in-memory list before calling `saveState`, ensuring consistency between the central index and individual job files.

Retrieving the current job list is handled by `listJobs` in **`state.mjs`** (lines 49-51), which parses [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) and returns an array of job metadata objects.

```javascript
import { listJobs } from "./state.mjs";

const jobs = listJobs(workspaceRoot);
console.log(jobs.map(j => `${j.id}: ${j.status}`));
// Output: ["job-abc123: completed", "job-def456: running"]

```

## Summary

- **The Codex plugin tracks job status** using a deterministic state directory derived from the workspace root, configurable via `CLAUDE_PLUGIN_DATA` or falling back to `/tmp/codex-companion/state`.
- **Individual job records** are stored as JSON files (`<jobId>.json`) with companion logs (`<jobId>.log`), manipulated through utilities in `state.mjs` and `tracked-jobs.mjs`.
- **Lifecycle management** is orchestrated by `runTrackedJob`, which handles initialization, execution, and final state persistence atomically.
- **Real-time updates** use `createJobProgressUpdater` to patch specific fields like `phase` and `threadId` without rewriting the entire job history.
- **Automatic pruning** keeps the central [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) index bounded to 50 entries, deleting older job files and logs to manage disk usage.

## Frequently Asked Questions

### Where does the Codex plugin store job records?

Job records are stored in a workspace-specific state directory determined by the `resolveStateDir` function in **`plugins/codex/scripts/lib/state.mjs`**. The path defaults to `/tmp/codex-companion/state` hashed by workspace root, or uses the path specified in the `CLAUDE_PLUGIN_DATA` environment variable. Each job receives a `<jobId>.json` file for metadata and a `<jobId>.log` file for human-readable output.

### How does the plugin prevent data loss during concurrent updates?

The system uses atomic file operations and a central `upsertJob` function that sequentially updates the in-memory job list before calling `saveState`. While the source code does not implement file locking, the design minimizes race conditions by writing individual job JSON files independently of the central [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) index, and by keeping the index write operations lightweight and fast.

### What is the maximum number of jobs retained in the state index?

The `saveState` function in **`state.mjs`** maintains a bounded list of **50 recent jobs** in [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json). When the limit is exceeded, older entries are removed from the index, and the corresponding `.json` and `.log` files are automatically pruned from the filesystem to reclaim storage space.

### How can I programmatically check the status of a specific job?

Use the `listJobs` export from **`plugins/codex/scripts/lib/state.mjs`** to retrieve all current jobs, then filter by `job.id`. For real-time monitoring during job execution, callers can inspect the specific `<jobId>.json` file directly or use the `createJobProgressUpdater` pattern to subscribe to change events emitted by the running task.