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

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}.

// 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.

// 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:

{
  "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:

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)
  • 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →