How Codex Job Results Are Stored and Retrieved in openai/codex-plugin-cc

Codex job results are persisted as JSON files in a per-repository state directory, indexed by a central state.json manifest, and retrieved via a two-step lookup that resolves the job ID against the manifest before loading the full payload.

When a Codex command such as review, task, or transfer completes in the openai/codex-plugin-cc repository, the companion script creates a durable job record that enables post-execution analysis and auditing. Understanding how Codex job results are stored and retrieved is essential for building integrations that consume Codex outputs programmatically or inspect historical runs via the CLI.

State Directory Structure and Location

Resolving the State Directory

The storage location is determined by state.resolveStateDir() in plugins/codex/scripts/lib/state.mjs. This function constructs a path from the repository root, a hash of that path, and either the CLAUDE_PLUGIN_DATA environment variable or a fallback temporary directory at /tmp/codex-companion.

File Layout and Persistence

Within the state directory, the system maintains a jobs/ subdirectory containing paired files for each execution:

  • <job-id>.json — Stores the full JSON payload including the raw result, rendered markdown, timestamps, and execution metadata. Written by state.writeJobFile().
  • <job-id>.log — Contains plain-text progress lines captured during execution, created by tracked-jobs.createJobLogFile().

A top-level state.json file acts as a manifest, maintaining a rolling array of the most recent jobs (default maximum of 50 entries) with lightweight metadata for status and cancel commands.

Job Record Creation and Storage

Writing the Job File

When tracked-jobs.runTrackedJob() executes, it creates an initial running record and persists it via writeJobFile(). After the Codex process terminates, the companion updates the file with the final state:

// After the Codex runner finishes (tracked-jobs.mjs)
writeJobFile(job.workspaceRoot, job.id, {
  ...runningRecord,
  status: completionStatus,
  threadId: execution.threadId,
  turnId: execution.turnId,
  phase: completionStatus === "completed" ? "done" : "failed",
  completedAt,
  result: execution.payload,      // Full Codex output
  rendered: execution.rendered,   // Markdown version
});

Updating the State Manifest

Simultaneously, state.upsertJob() updates state.json to include the job's metadata (ID, status, timestamps), ensuring the status command can list recent jobs without parsing individual JSON files.

Retrieving Stored Results

The Result Sub-Command Flow

The result sub-command in codex-companion.mjs (around line 1055) implements a two-stage retrieval process:

const { workspaceRoot, job } = resolveResultJob(cwd, reference);
const storedJob = readStoredJob(workspaceRoot, job.id);
  • resolveResultJob() (in job-control.mjs) selects a finished job (status of completed, failed, or cancelled) by ID or prefix match.
  • readStoredJob() loads the <job-id>.json file from the state directory.

Rendering and Presentation

For human consumption, renderStoredJobResult() in render.mjs formats the stored payload into a readable view containing the verdict, summary, findings, artifacts, and follow-up commands. For programmatic access, the --json flag returns the raw payload directly.

Programmatic Access Patterns

Fetching Results via CLI

To retrieve a specific job from the terminal:


# Display formatted result

codex result task-abc123

# Get raw JSON payload

codex result task-abc123 --json

Accessing Results via Node.js API

For integration with other tools, import the state and job-control modules:

import { resolveJobFile, readJobFile } from "./lib/state.mjs";
import { resolveResultJob } from "./lib/job-control.mjs";

async function getJobResult(cwd, jobId) {
  const { workspaceRoot, job } = resolveResultJob(cwd, jobId);
  const resultPath = resolveJobFile(workspaceRoot, job.id);
  const payload = readJobFile(resultPath); // Contains `result` and `rendered`
  return payload;
}

Key Source Files and Responsibilities

  • state.mjs — Handles state directory resolution, state.json manifest management, and per-job file I/O (writeJobFile, readJobFile).
  • tracked-jobs.mjs — Orchestrates job lifecycle, creates log files, and persists final results via writeJobFile().
  • job-control.mjs — Provides resolveResultJob() for job selection and readStoredJob() for file retrieval.
  • render.mjs — Transforms stored JSON into human-readable output via renderStoredJobResult().
  • codex-companion.mjs — CLI entry point that wires the result command to the storage subsystem.

Summary

  • Codex job results are stored as JSON files in a repository-specific state directory under <state-dir>/jobs/<job-id>.json.
  • A state.json manifest tracks the most recent 50 jobs for quick listing and status checks.
  • Each job includes a companion .log file containing execution progress.
  • Retrieval requires resolving the job ID against the manifest, then loading the specific JSON payload via readStoredJob().
  • Results can be rendered as formatted markdown or exported as raw JSON using the --json flag.

Frequently Asked Questions

Where are Codex job results physically stored?

The companion script writes results to a directory resolved by state.resolveStateDir() in plugins/codex/scripts/lib/state.mjs. This location combines the repository root path hash with the CLAUDE_PLUGIN_DATA environment variable or falls back to /tmp/codex-companion, creating a jobs/ subdirectory containing <job-id>.json and <job-id>.log files.

How long are job results retained?

The system maintains a rolling window of the most recent 50 jobs in state.json for quick reference, but the individual JSON and log files in the jobs/ directory persist indefinitely until manually deleted or the state directory is cleared.

Can I retrieve a job result using only a partial ID?

Yes. The resolveResultJob() function in job-control.mjs accepts job ID prefixes and resolves them to full IDs, provided the prefix uniquely identifies a finished job (completed, failed, or cancelled).

What is the difference between the result and rendered fields in the stored JSON?

The result field contains the raw, structured payload returned by the Codex model, while the rendered field contains a pre-formatted markdown representation suitable for terminal display without additional processing.

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 →