# How OpenAI Codex Plugin Manages Background Job State and Persistence

> Discover how the OpenAI Codex plugin manages background job state and persistence. Learn about its JSON file system, automatic pruning, and detailed log files for efficient workflow management.

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

---

**The OpenAI Codex plugin persists background job state using a JSON-based file system approach, storing workspace-specific data in hashed directories and automatically pruning records to the 50 most recent jobs while maintaining individual log files for detailed introspection.**

The `openai/codex-plugin-cc` repository implements a durable state management system that ensures task continuity across plugin sessions. Understanding how the OpenAI Codex plugin manages background job state and persistence reveals a lightweight yet robust architecture that prevents data loss and unbounded disk growth through atomic file operations and automatic cleanup.

## State Storage Location and Directory Resolution

The plugin isolates state data by workspace to prevent collisions between different projects.

In `plugins/codex/scripts/lib/state.mjs`, the `resolveStateDir` function (lines 29-44) constructs a unique directory path by creating a slug from the workspace name and hashing the absolute path. This directory defaults to either a user-provided `CLAUDE_PLUGIN_DATA` environment variable or falls back to `/tmp/codex-companion`.

### Workspace Isolation Strategy

Each workspace receives its own isolated storage directory. The hashing mechanism ensures that even workspaces with identical names in different locations receive distinct state directories, preventing cross-contamination of job data.

### Environment Configuration

Developers can override the default temporary location by setting the `CLAUDE_PLUGIN_DATA` variable. This flexibility allows teams to place state files on persistent volumes or shared network storage when needed.

## State File Structure and Schema

The plugin maintains a central [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) file that serves as the source of truth for all background operations.

### The state.json Format

As defined in the `defaultState` object (lines 19-27 of `state.mjs`), the state file contains three top-level properties:

- `version`: The schema version for migration compatibility
- `config`: Plugin-wide settings such as `stopReviewGate`
- `jobs`: An array of job records tracking timestamps, status, progress markers, and log file references

### Individual Job Files

Beyond the central state file, each job receives dedicated storage. The `resolveJobsDir` function creates a `jobs` subdirectory containing:

- `<jobId>.json`: The job's payload and metadata
- `<jobId>.log`: Human-readable execution logs

These files are created via `writeJobFile` and `createJobLogFile`, and removed during pruning operations.

## Loading, Saving, and Pruning State

The plugin implements synchronous filesystem operations to guarantee state persistence before process termination.

### Atomic State Updates

The `loadState` function (lines 58-78) reads the JSON file or returns the default structure if the file is missing or corrupted. The `saveState` function (lines 92-115) writes the complete state atomically, ensuring that concurrent modifications do not corrupt the data.

### Job Limits and Cleanup

To prevent unbounded growth, `saveState` enforces a `MAX_JOBS` limit of 50 records. When the job array exceeds this threshold, the plugin:

1. Removes the oldest job entries
2. Deletes orphaned JSON payload files
3. Cleans up associated log files

This pruning occurs automatically during every save operation.

## Job Lifecycle Management

The `plugins/codex/scripts/lib/tracked-jobs.mjs` module provides high-level abstractions that orchestrate the complete job execution flow.

### Job Creation and Initialization

The `createJobRecord` function initializes new jobs with timestamps and the current session ID (`CODEX_COMPANION_SESSION_ID`). This record is immediately persisted via `upsertJob`, which updates the job array in [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) while preserving existing entries.

### Progress Tracking and Updates

During execution, `createJobProgressUpdater` monitors changes in `phase`, `threadId`, or `turnId`. When detected, it invokes `upsertJob` to patch the specific job entry without rewriting the entire state history.

### Execution and Finalization

The `runTrackedJob` function (lines 42-104 and 142-176) implements the complete lifecycle:

1. Writes an initial "running" status
2. Streams log output to the dedicated log file
3. Captures the final result (exit status, payload, rendered output)
4. Updates status to `completed` or `failed`
5. Appends final log blocks

Errors are caught, recorded in the state, and re-thrown to ensure the calling code receives failure notifications while the persistent record remains accurate.

## Practical Implementation Example

The following example demonstrates initializing, running, and inspecting a tracked job:

```javascript
import {
  upsertJob,
  loadState,
  resolveStateDir,
  writeJobFile,
  resolveJobLogFile,
} from "./state.mjs";
import { runTrackedJob } from "./tracked-jobs.mjs";

/* 1️⃣ Initialise a job */
const workspaceRoot = process.cwd();
const jobId = generateJobId("example");
const job = {
  id: jobId,
  workspaceRoot,
  title: "Sample background task",
  status: "queued",
};

/* 2️⃣ Persist the initial record */
upsertJob(workspaceRoot, job);
writeJobFile(workspaceRoot, jobId, job);

/* 3️⃣ Run the task with automatic state tracking */
await runTrackedJob(job, async () => {
  // Simulate work
  await new Promise((r) => setTimeout(r, 1500));
  return {
    exitStatus: 0,
    payload: { result: "OK" },
    rendered: "Task completed successfully",
    threadId: "t1",
    turnId: "turn-42",
    summary: "All good",
  };
}, { logFile: resolveJobLogFile(workspaceRoot, jobId) });

/* 4️⃣ Inspect the persisted state */
console.log("Current state:", loadState(workspaceRoot));

```

## Summary

- The plugin stores state in [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) within a workspace-specific directory resolved via `resolveStateDir` in `plugins/codex/scripts/lib/state.mjs`
- Each job maintains separate `.json` and `.log` files in the `jobs` subdirectory
- The system enforces a 50-job retention limit (`MAX_JOBS`) with automatic cleanup of orphaned files
- `runTrackedJob` in `tracked-jobs.mjs` provides atomic lifecycle management from initialization through completion
- All state operations use synchronous Node.js `fs` APIs to ensure durability before process exit

## Frequently Asked Questions

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

The plugin stores state in a dedicated directory under `/tmp/codex-companion` by default, or under the path specified by the `CLAUDE_PLUGIN_DATA` environment variable. Within this directory, each workspace receives a unique subdirectory based on a hash of its absolute path, containing the [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) file and a `jobs` folder for individual job files.

### What is the maximum number of jobs the plugin retains?

The plugin maintains a maximum of 50 jobs (`MAX_JOBS`) in the state file. When this limit is exceeded, the oldest job records are automatically pruned during the next `saveState` operation, along with their associated JSON payload and log files.

### How does the plugin handle concurrent state updates?

The plugin uses synchronous file operations (`fs.readFileSync` and `fs.writeFileSync`) to implement atomic read-modify-write cycles. While this blocks the event loop during I/O, it guarantees that state updates complete before the process terminates, preventing data corruption in single-process scenarios.

### What happens to log files when jobs are pruned?

When the `saveState` function prunes jobs to enforce the `MAX_JOBS` limit, it removes the corresponding `<jobId>.log` files from the `jobs` directory. This ensures that storage usage remains bounded and prevents accumulation of orphaned log data from completed or failed tasks.