# Codex Plugin Job State Files: Format, Structure, and Storage Location

> Explore the format, structure, and storage location of Codex plugin job state files. Learn how async jobs are saved as pretty-printed JSON in deterministic paths.

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

---

**The Codex plugin persists each asynchronous job as a separate, pretty-printed JSON file in a workspace-specific `jobs` subdirectory, using deterministic hash-based directory paths and UUID-like filenames with `.json` extensions.**

The OpenAI Codex plugin manages long-running operations through persistent job state files stored on local disk. These JSON files track the lifecycle of asynchronous tasks, enabling recovery, status monitoring, and cancellation across plugin restarts.

## Directory Structure and Storage Location

The plugin implements a hierarchical directory structure that isolates job state by workspace to prevent cross-project contamination.

### Root State Directory Resolution

The base storage location is determined by the `resolveStateDir(cwd)` function in `plugins/codex/scripts/lib/state.mjs` (lines 30-44). The resolution logic follows this priority:

- **Environment Variable**: If `CLAUDE_PLUGIN_DATA` is set, the state root is `$CLAUDE_PLUGIN_DATA/state`
- **Fallback**: Otherwise, it uses `os.tmpdir()/codex-companion`

Within this root, the plugin creates a unique directory for each workspace using a slug of the workspace basename combined with a SHA-256 hash of its canonical path: `${slug}-${hash}`.

```javascript
// From state.mjs - resolveStateDir implementation
const slug = basename(cwd).toLowerCase().replace(/[^a-z0-9]/g, '-');
const hash = createHash('sha256').update(resolve(cwd)).digest('hex').slice(0, 12);
const stateDir = join(rootStateDir, `${slug}-${hash}`);

```

### Jobs Subdirectory Declaration

The plugin stores all job files in a dedicated subdirectory named **`jobs`**, defined by the constant `JOBS_DIR_NAME = "jobs"` (line 12-13 in `state.mjs`). The full path is constructed as `path.join(resolveStateDir(cwd), "jobs")` (lines 50-52). The directory is created automatically when job operations first execute via the `ensureStateDir` helper.

## File Naming Convention

Each job receives a unique identifier via the `generateJobId()` function, producing UUID-like strings (e.g., `"job-kr1x2y3-abc123"`). The `resolveJobFile(cwd, jobId)` function (lines 88-91) constructs the complete file path following the pattern:

```

<stateRoot>/<slug-hash>/jobs/<jobId>.json

```

This ensures every job state file has a `.json` extension and resides in the workspace-isolated jobs directory.

## JSON Format and Payload Structure

The plugin writes human-readable JSON with consistent formatting standards to facilitate debugging and manual inspection.

### Writing Job State Files

The `writeJobFile(cwd, jobId, payload)` function (lines 66-70) serializes job data using `JSON.stringify(payload, null, 2)`, producing pretty-printed output with 2-space indentation and a trailing newline for POSIX compliance.

```javascript
// From state.mjs lines 66-70
export async function writeJobFile(cwd, jobId, payload) {
  const jobFile = resolveJobFile(cwd, jobId);
  await writeFile(jobFile, JSON.stringify(payload, null, 2) + '\n');
  return jobFile;
}

```

### Reading Job State Files

Retrieval is handled by `readJobFile(jobFile)` (lines 73-75), which parses the stored JSON back into a JavaScript object using `JSON.parse()` after reading the file contents.

### Standard Payload Schema

While the plugin does not enforce a rigid schema, the `upsertJob` helper (implemented in the codebase) automatically injects standard metadata before persisting. A typical job state file contains:

```json
{
  "id": "job-kr1x2y3-abc123",
  "createdAt": "2024-01-15T10:30:00.000Z",
  "updatedAt": "2024-01-15T10:35:00.000Z",
  "status": "running",
  "description": "Running Codex analysis"
}

```

The `createdAt` and `updatedAt` fields are ISO 8601 timestamps automatically added during the upsert operation, while additional fields are supplied by the caller based on specific job requirements.

## Practical Code Examples

The following demonstrates the complete lifecycle of job state persistence using the public API from `state.mjs`:

```javascript
import { 
  writeJobFile, 
  readJobFile, 
  resolveJobFile,
  generateJobId 
} from "./plugins/codex/scripts/lib/state.mjs";

// Generate unique job identifier
const jobId = generateJobId();  // → "job-a1b2c3d-xyz789"

// Persist initial job state
await writeJobFile(process.cwd(), jobId, {
  id: jobId,
  status: "running",
  description: "Processing repository analysis",
  createdAt: new Date().toISOString(),
  updatedAt: new Date().toISOString()
});

// Retrieve absolute file path
const jobPath = resolveJobFile(process.cwd(), jobId);
console.log(jobPath); 
// → /tmp/codex-companion/my-project-a1b2c.../jobs/job-a1b2c3d-xyz789.json

// Read back the stored state
const jobData = await readJobFile(jobPath);
console.log(jobData.status); // "running"

```

## Summary

- **Location**: Job state files reside in `$CLAUDE_PLUGIN_DATA/state/<slug-hash>/jobs/` or `os.tmpdir()/codex-companion/<slug-hash>/jobs/`
- **Naming**: Files use UUID-like job identifiers with `.json` extensions (e.g., [`job-abc123.json`](https://github.com/openai/codex-plugin-cc/blob/main/job-abc123.json))
- **Format**: Pretty-printed JSON with 2-space indentation and trailing newlines
- **Schema**: Flexible structure with mandatory `id`, `createdAt`, and `updatedAt` ISO timestamp fields
- **Key Functions**: `writeJobFile()`, `readJobFile()`, and `resolveJobFile()` in `plugins/codex/scripts/lib/state.mjs`

## Frequently Asked Questions

### Where are Codex plugin job state files stored when the CLAUDE_PLUGIN_DATA environment variable is not set?

When `CLAUDE_PLUGIN_DATA` is undefined, the plugin falls back to the system temporary directory via `os.tmpdir()`, appending `/codex-companion` as the root state directory. Within this location, it creates a workspace-specific subdirectory using a SHA-256 hash of the canonical path to prevent collisions between different projects.

### What is the exact JSON schema enforced for Codex plugin job state files?

The plugin does not enforce a rigid schema validation. However, the `upsertJob` helper automatically injects three standard fields: `id` (the job identifier), `createdAt` (ISO timestamp of creation), and `updatedAt` (ISO timestamp of last modification). Additional fields such as `status`, `description`, or custom metadata are supplied by the calling code and stored as-is in the JSON payload.

### How does the Codex plugin prevent job state conflicts between different workspaces?

The plugin isolates job state through a deterministic hashing mechanism. The `resolveStateDir()` function generates a unique directory name by combining a slug of the workspace basename with a 12-character SHA-256 hash of the workspace's canonical absolute path. This ensures that `/home/user/project-a` and `/home/user/project-b` receive distinct state directories even if the folder names are identical.

### Can Codex plugin job state files be safely deleted manually?

Yes, job state files are standard JSON files that can be deleted manually without corrupting the plugin installation. However, deleting a job file while the corresponding asynchronous operation is active will cause the plugin to lose track of that job's status, potentially leaving background processes orphaned. Safe deletion is recommended only for completed or failed jobs that are no longer needed for status monitoring.