# How the Codex Plugin Persists Job State Across Claude Code Restarts

> Discover how the Codex plugin uses a file-based state store to persist job data and recover status, phase, and output after Claude Code restarts.

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

---

**The Codex plugin uses a deterministic, file-based state store located in `plugins/codex/scripts/lib/state.mjs` to write atomic JSON records and append-only log files, enabling full recovery of job status, phase, and output history after Claude Code restarts.**

The `openai/codex-plugin-cc` repository implements a robust persistence layer that survives process termination, container restarts, and user-initiated session resets. By maintaining durable JSON records and log files outside the Claude Code process, the plugin ensures that long-running jobs can be tracked and resumed without data loss.

## How State Persistence Works

The persistence mechanism relies on two core modules: **`state.mjs`** for low-level file operations and **`tracked-jobs.mjs`** for higher-level job lifecycle management. Together, they create a deterministic directory structure, write atomic updates, and prune stale records to prevent unbounded growth.

## The State Storage Architecture

### Resolving the State Directory

The first step in persistence is establishing a stable storage location. The `resolveStateDir()` function in `state.mjs` (lines 29-44) builds a unique directory name derived from a hash of the workspace path. This directory is then stored under the environment variable **`CLAUDE_PLUGIN_DATA`**, falling back to a temporary location in `/tmp` if unset.

This hashing strategy guarantees that the same workspace always maps to the same directory, even across different Claude Code sessions or system reboots.

### Loading and Initializing State

When the plugin initializes, `loadState()` (lines 58-77) reads the [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) file from the resolved directory. If the file is missing or corrupted, the function gracefully falls back to a default skeleton state, ensuring the plugin never crashes on malformed data.

```javascript
// Located in plugins/codex/scripts/lib/state.mjs
const state = loadState(workspaceRoot);
// Returns the current state or a default structure if state.json is absent

```

## Writing and Updating Job Records

### Atomic Job File Creation

Each job receives its own dedicated JSON file (`<jobId>.json`) inside a `jobs/` subdirectory. The `writeJobFile()` function (lines 66-71) serializes the job object and writes it atomically to prevent corruption during unexpected shutdowns. The file path is derived from `resolveJobFile()`, ensuring consistent naming conventions.

```javascript
// Creating a persistent job record
import { generateJobId, upsertJob, writeJobFile } from "./state.mjs";

const jobId = generateJobId();
const job = {
  id: jobId,
  workspaceRoot: process.cwd(),
  title: "Run unit tests",
  status: "pending",
};

await upsertJob(process.cwd(), job);        // Updates global state.json
await writeJobFile(process.cwd(), jobId, job); // Creates jobs/<jobId>.json

```

### Global State Updates and Pruning

The `upsertJob()` function calls `updateState()` (lines 29-33 and 92-104) to synchronize the global state. This operation:
1. Loads the full state from disk
2. Merges the supplied patch (e.g., new `phase`, `status`, or `pid`)
3. Prunes the job list to the most recent **50 entries**
4. Writes the updated [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json)
5. Cleans up stale job files to prevent disk bloat

This pruning mechanism ensures that the Codex plugin maintains constant memory and disk usage regardless of how many jobs are run over time.

## Logging and Session Correlation

Beyond JSON state files, the plugin maintains human-readable audit trails. The `createJobLogFile()` function in `tracked-jobs.mjs` (lines 51-58) creates a plain-text `<jobId>.log` file in the same `jobs/` directory. Subsequent calls to `appendLogLine()` and `appendLogBlock()` append output to this file, providing a replayable history that survives restarts.

Additionally, the plugin supports session correlation through the optional **`sessionId`** (read from `CODEX_COMPANION_SESSION_ID`). This value is stored with each job record (lines 60-68 in `tracked-jobs.mjs`), allowing the plugin to filter jobs belonging to the current Claude Code session when enumerating active or historical work.

## Recovering Jobs After Restart

When a new Claude Code session starts, the plugin calls `listJobs()` (lines 49-52), which returns `loadState().jobs`. Because the state directory lives outside the process—in the OS temp folder or the user-provided `CLAUDE_PLUGIN_DATA` path—the data persists and can be reconstructed into live job objects.

```javascript
// Retrieving all jobs after a restart
import { listJobs } from "./state.mjs";

const jobs = listJobs(process.cwd());
console.log("Recovered jobs:", jobs);
// Restores job objects with their original status, phase, and metadata

```

## Code Examples

The following snippets demonstrate the full persistence lifecycle: creation, live updates, and recovery.

**1. Create a new job record with associated log file:**

```javascript
import { generateJobId, upsertJob, writeJobFile, createJobLogFile } from "./state.mjs";

const cwd = process.cwd();
const jobId = generateJobId();
const job = {
  id: jobId,
  workspaceRoot: cwd,
  title: "Run unit tests",
  status: "pending",
};

upsertJob(cwd, job);
writeJobFile(cwd, jobId, job);
const logFile = createJobLogFile(cwd, jobId, job.title);

```

**2. Update progress while the job runs:**

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

const progress = createJobProgressUpdater(cwd, jobId);
progress({ phase: "install", message: "Installing dependencies…" });
progress({ phase: "test", message: "Running tests…" });
// Automatically persists phase changes to state.json

```

**3. Retrieve all jobs after a restart:**

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

const jobs = listJobs(cwd);
console.log("Recovered jobs:", jobs);

```

## Summary

- **Deterministic storage**: The `resolveStateDir()` function in `state.mjs` creates a hashed directory path based on the workspace root, ensuring consistent storage locations across Claude Code restarts.
- **Atomic writes**: Job data is written to individual JSON files using `writeJobFile()`, while global state updates occur through `updateState()` with automatic pruning to 50 entries.
- **Graceful degradation**: `loadState()` falls back to default skeleton state if [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) is missing or corrupted, preventing crashes on bad files.
- **Audit trails**: `createJobLogFile()` and append operations in `tracked-jobs.mjs` provide durable, human-readable logs that survive process termination.
- **Session awareness**: The optional `sessionId` from `CODEX_COMPANION_SESSION_ID` allows filtering jobs by Claude Code session.

## Frequently Asked Questions

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

The plugin stores job state in a directory resolved by `resolveStateDir()` in `plugins/codex/scripts/lib/state.mjs`. This directory defaults to the path specified in the `CLAUDE_PLUGIN_DATA` environment variable, or falls back to a hashed subdirectory under `/tmp`. Inside this directory, you will find [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) (global state) and a `jobs/` subdirectory containing individual `<jobId>.json` files and `<jobId>.log` files.

### How does the plugin prevent data loss if Claude Code crashes?

The implementation uses atomic file writes and defensive error handling. Specifically, `writeJobFile()` writes job data atomically to prevent partial writes, while `loadState()` catches corrupted or missing files and returns a default state structure rather than throwing exceptions. This ensures that even if Claude Code terminates unexpectedly, the existing state files remain intact and readable on the next startup.

### What is the maximum number of jobs the Codex plugin remembers?

The plugin maintains a sliding window of the **50 most recent jobs**. The `updateState()` function (lines 92-104 in `state.mjs`) automatically prunes older entries when writing updates to [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json), and synchronously cleans up the associated stale job files from the `jobs/` directory. This prevents unbounded disk usage while preserving recent work history.

### How does the plugin distinguish between different Claude Code sessions?

The plugin reads the optional `CODEX_COMPANION_SESSION_ID` environment variable and stores it as a `sessionId` field in each job record (as implemented in `tracked-jobs.mjs`, lines 60-68). This identifier allows the plugin to filter and correlate jobs with specific Claude Code sessions, enabling session-specific job listings while still maintaining a complete historical record across all sessions.