# How the Codex Plugin Handles Job State Persistence Across Restarts

> Learn how the Codex plugin ensures job state persistence across restarts by storing background job metadata in a JSON file. Recover job status, config, and output after any interruption.

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

---

**The Codex plugin stores background job metadata in a JSON file inside a repository-specific state directory, enabling full recovery of job status, configuration, and output files after editor or system restarts.**

The `openai/codex-plugin-cc` repository provides a Claude Code plugin that manages long-running Codex tasks in the background. To ensure **job state persistence across restarts**, the plugin implements a durable storage layer in `plugins/codex/scripts/lib/state.mjs` that serializes job records, configuration flags, and runtime logs to disk using a deterministic, repository-scoped directory structure.

## State Directory Resolution and Isolation

The plugin derives a unique storage location for every workspace to prevent collisions between different repositories.

### Environment Variable and Fallback Paths

The root state directory is determined by the `CLAUDE_PLUGIN_DATA` environment variable (referenced internally as `PLUGIN_DATA_ENV`). If this variable is unset, the plugin falls back to the system temporary directory via `os.tmpdir()`, appending `codex-companion/state` to the path.

### Repository-Specific Hashing

To isolate state between different repositories, the plugin resolves the workspace's canonical path using `fs.realpathSync.native` and computes a short SHA-256 hash of that path. This produces unique directories such as:

```bash
/tmp/codex-companion/state/my-repo-7f3a9b2c8e1d2f3a

```

The functions `resolveStateDir` and `resolveStateFile` handle this path construction, ensuring that multiple copies of the same repository opened in different locations maintain separate state stores.

## State File Structure and Versioning

The central [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) file (defined by the constant `STATE_FILE_NAME`) contains a versioned JSON object with the following structure:

```json
{
  "version": 1,
  "config": { "stopReviewGate": false },
  "jobs": [ … ]
}

```

The `STATE_VERSION` constant is set to `1`, enabling future schema migrations. Each entry in the `jobs` array is a job record containing fields such as `id`, `createdAt`, `updatedAt`, `status`, `model`, and `logFile`.

## Saving, Pruning, and Loading Job State Across Restarts

### The Upsert and Save Cycle

When a job is created or updated, the `upsertJob` function modifies the in-memory state and triggers `saveState`, which synchronously rewrites the entire [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) file to disk using `fs.writeFileSync`.

```javascript
import { upsertJob, resolveJobLogFile } from "./lib/state.mjs";

const jobId = `review-${Date.now()}`;
await upsertJob(process.cwd(), {
  id: jobId,
  type: "review",
  status: "running",
  model: "gpt-4",
  logFile: resolveJobLogFile(process.cwd(), jobId)
});

```

### Automatic Pruning of Old Jobs

Before writing, the `pruneJobs` function truncates the job list to the most recent `MAX_JOBS` (default 50). Older jobs are removed from the JSON array, and their associated files are deleted via `removeJobFile` and `removeFileIfExists`, keeping the on-disk footprint bounded.

### Startup Recovery with loadState

On every command invocation, `loadState` reads [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) from the resolved state directory. If the file does not exist, it returns a `defaultState` object, ensuring that missing fields (such as newly added configuration flags) are populated with safe defaults.

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

// After a restart, list the recent jobs
const recentJobs = listJobs(process.cwd());
console.log(recentJobs.map(j => `${j.id}: ${j.status}`));

```

## Job-Specific File Management

Beyond the central state file, each job receives two dedicated files in the `jobs/` subdirectory, resolved by `resolveJobsDir`:

- **JSON payload file** (`<jobId>.json`): Stores the raw Codex response, accessible via `resolveJobFile`.
- **Log file** (`<jobId>.log`): Captures streaming output during execution, accessible via `resolveJobLogFile`.

These files survive process termination, allowing commands like `/codex:status` and `/codex:result` to retrieve historical data after a restart.

```javascript
import { readJobFile, resolveJobFile } from "./lib/state.mjs";

// Retrieve the stored result of a finished job
const payload = readJobFile(resolveJobFile(process.cwd(), "job-abcdef-123"));
console.log(payload.output);

```

## Configuration Persistence

User settings such as the review-gate flag are stored inside [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) under the `config` key. The `setConfig` and `getConfig` functions read and write these values, ensuring that configuration changes persist across editor sessions and system reboots.

## Cross-Process Safety Considerations

All directory creation uses `{ recursive: true }` to ensure path existence. File writes are performed synchronously (`fs.writeFileSync`) to prevent interleaved writes during sequential operations within the same CLI process. While the plugin is not designed for concurrent writes from multiple processes, this synchronous approach guarantees atomicity for single-process operations.

## Summary

- The plugin stores state in a directory determined by `CLAUDE_PLUGIN_DATA` or a fallback temporary path, hashed by repository location to ensure isolation.
- Job metadata lives in [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) with schema versioning (`STATE_VERSION = 1`), while individual job outputs reside in separate `.json` and `.log` files.
- The `pruneJobs` mechanism limits storage to 50 recent jobs (`MAX_JOBS`), deleting older artifacts automatically.
- `loadState` and `defaultState` ensure graceful recovery and forward compatibility when loading persisted data.
- Configuration settings are co-located with job data in [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json), surviving restarts via the same persistence layer.

## Frequently Asked Questions

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

The plugin stores data in a subdirectory under `CLAUDE_PLUGIN_DATA` (or `os.tmpdir()/codex-companion/state` if unset). The exact directory name incorporates a SHA-256 hash of the repository's canonical path to ensure isolation between different workspace locations.

### How many jobs are retained after a restart?

By default, the plugin retains the 50 most recent jobs (`MAX_JOBS`). Older jobs are pruned from [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) and their associated files are deleted during the save operation to prevent unbounded disk growth.

### Can job state survive a system reboot?

Yes. Because all state is written to disk via synchronous file operations and stored in the file system (either in the configured data directory or the temp folder), job records, logs, and configuration settings persist through system reboots, editor restarts, and process crashes.

### How does the plugin handle concurrent writes to state files?

The plugin uses synchronous write operations (`fs.writeFileSync`) to prevent corruption during sequential operations within a single process. However, it is not designed for concurrent access from multiple processes; simultaneous writes from different CLI instances could result in race conditions.