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_DATAis 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/oros.tmpdir()/codex-companion/<slug-hash>/jobs/ - Naming: Files use UUID-like job identifiers with
.jsonextensions (e.g.,job-abc123.json) - Format: Pretty-printed JSON with 2-space indentation and trailing newlines
- Schema: Flexible structure with mandatory
id,createdAt, andupdatedAtISO timestamp fields - Key Functions:
writeJobFile(),readJobFile(), andresolveJobFile()inplugins/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →