# Codex-Plugin-CC State Persistence: Where and How Plugin Data is Stored

> Discover where and how codex-plugin-cc stores plugin data. Learn about state.json persistence and custom data locations via CLAUDE_PLUGIN_DATA.

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

---

**The codex-plugin-cc persists all runtime configuration, job metadata, and per-job files to a JSON file named [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) located in a workspace-specific directory under the OS temporary directory by default, or under the path specified by the `CLAUDE_PLUGIN_DATA` environment variable.**

The `openai/codex-plugin-cc` repository implements a deterministic persistence layer to maintain plugin state across process restarts. All state management logic lives in `plugins/codex/scripts/lib/state.mjs`, which handles directory resolution, atomic updates, and automatic pruning of stale job data. Understanding this codex-plugin-cc state persistence mechanism is essential for debugging, backup strategies, and containerized deployments.

## State Storage Location and Directory Resolution

The plugin isolates state per workspace to prevent collisions between different repositories on the same machine.

### Workspace-Specific Directory Structure

The `resolveStateDir(cwd)` function (lines 29‑44 in `plugins/codex/scripts/lib/state.mjs`) generates a unique directory name derived from the workspace root:

1. **Finds the workspace root** via `resolveWorkspaceRoot` (implemented in `workspace.mjs`).
2. **Normalizes the path** using `realpathSync` to resolve symlinks.
3. **Creates a slug** combining the folder name and the first 16 characters of a SHA‑256 hash of the absolute path.
4. **Selects the root state directory**:
   - If `CLAUDE_PLUGIN_DATA` is set, uses `<env>/state`.
   - Otherwise falls back to `os.tmpdir()/codex‑companion`.
5. **Returns** the fully resolved path as `<stateRoot>/<slug‑hash>`.

This guarantees that two repositories named `project` in different locations never share state.

### Environment Variable Override

Set `CLAUDE_PLUGIN_DATA` to redirect all state storage to a custom location. This is critical for CI pipelines, Docker containers with mounted volumes, or user scripts requiring persistent storage outside the temporary directory.

## State File Structure and Schema

### The Main State File

The constant `STATE_VERSION = 1` (line 8) guards against future schema changes. The plugin writes all data to a file named [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) (line 11), located at the path returned by `resolveStateFile(cwd)` (lines 46‑48), which simply joins the state directory with the filename.

### Jobs Subdirectory

Per-job artifacts reside in a `jobs` subfolder (lines 12‑13). The function `resolveJobsDir(cwd)` (lines 50‑52) constructs this path, while `ensureStateDir(cwd)` (lines 54‑56) recursively creates both the state directory and the `jobs` subdirectory if they do not exist.

## Core Persistence Operations

### Loading State

The `loadState(cwd)` function (lines 58‑78) reads [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) from disk. If the file does not exist, it returns a fresh object from `defaultState()`. If JSON parsing fails due to corruption, the function gracefully falls back to defaults rather than crashing.

### Saving and Compacting State

`saveState(cwd, state)` (lines 92‑116) handles persistence with several safeguards:

- **Merges** the new state with defaults to ensure schema completeness.
- **Prunes** the job list to `MAX_JOBS` (50 entries), removing the oldest entries.
- **Deletes** stale job files and logs associated with pruned entries.
- **Writes** compacted, pretty‑printed JSON to [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json).

### Atomic Updates

For thread‑safe modifications, `updateState(cwd, mutate)` (lines 118‑122) implements an atomic load‑mutate‑save pattern. It loads the current state, applies the caller’s mutation function, and persists the result via `saveState`.

## Job-Specific Persistence

Individual job payloads and logs are stored separately from the main state file to prevent [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) from growing unbounded.

- **`writeJobFile(cwd, jobId, data)`** and **`readJobFile(cwd, jobId)`** (lines 66‑91) serialize job payloads to `<id>.json` inside the `jobs` directory.
- **`resolveJobFile(cwd, jobId)`** returns the absolute path to a job’s JSON file.
- **`resolveJobLogFile(cwd, jobId)`** (lines 166‑191) returns the path to `<id>.log` for streaming runtime logs.

## Configuration Persistence

Plugin configuration values are stored within the `config` subtree of [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json).

- **`getConfig(cwd)`** (lines 53‑64) retrieves the current configuration object.
- **`setConfig(cwd, key, value)`** updates a specific key and persists the change.

## Practical Usage Examples

The following example demonstrates typical interactions with the persistence layer:

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

// 1. Read the current config (e.g., stop‑review‑gate flag)
const { stopReviewGate } = getConfig(process.cwd());

// 2. Toggle a config key
await setConfig(process.cwd(), "stopReviewGate", !stopReviewGate);

// 3. Create a new job entry and persist it
const jobId = generateJobId("review");
await upsertJob(process.cwd(), {
  id: jobId,
  status: "queued",
  description: "Running review on the latest commit",
});

// 4. Store arbitrary payload for that job
await writeJobFile(process.cwd(), jobId, { steps: [], result: null });

// 5. Get the path to the job’s log file (the runtime writes to it directly)
const logPath = resolveJobLogFile(process.cwd(), jobId);

```

All functions ultimately resolve paths via `resolveStateDir` and read/write JSON through the `loadState` and `saveState` mechanisms described above.

## Summary

- **Location**: State resides in [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) within a directory derived from the workspace path hash, defaulting to the OS temp directory (`/tmp/codex‑companion`) unless overridden by `CLAUDE_PLUGIN_DATA`.
- **Schema**: Versioned JSON (`STATE_VERSION = 1`) with a separate `jobs` subdirectory for per‑job files.
- **API**: Core functions in `plugins/codex/scripts/lib/state.mjs` include `loadState`, `saveState`, `updateState`, `writeJobFile`, and `resolveJobLogFile`.
- **Limits**: The plugin retains a maximum of 50 jobs (`MAX_JOBS`), automatically pruning older entries and their associated files on save.
- **Resilience**: The loader gracefully handles missing or corrupted JSON files by falling back to default state.

## Frequently Asked Questions

### Where does codex-plugin-cc store its state by default?

By default, the plugin stores state in the OS temporary directory under `codex‑companion/<workspace‑slug>/state.json`. The exact path is determined by hashing the canonical workspace root path to ensure isolation between different repositories.

### How can I change the state storage location for codex-plugin-cc?

Set the `CLAUDE_PLUGIN_DATA` environment variable to an absolute path. The plugin will create a `state` subdirectory inside that path and store all data there, bypassing the default temporary directory. This is essential for Docker containers or CI environments requiring persistent volumes.

### What happens if the state.json file becomes corrupted?

The `loadState` function in `plugins/codex/scripts/lib/state.mjs` catches JSON parse errors and returns a fresh default state object. This prevents the plugin from crashing on startup due to disk corruption or manual file edits, though it will result in loss of previous job history.

### How many jobs does the plugin keep in state?

The plugin maintains a maximum of 50 jobs in the state file, defined by the `MAX_JOBS` constant. When saving state, `saveState` automatically prunes entries beyond this limit and deletes their associated `.json` payload and `.log` files from the `jobs` subdirectory to prevent unbounded disk growth.