How Job Tracking and Persistence Work in the OpenAI Codex Plugin
The OpenAI Codex plugin uses a session-scoped UUID registry stored in memory and serialized to JSON via state.mjs to track job lifecycles, enabling crash recovery and graceful cancellation across the companion process and broker architecture.
The openai/codex-plugin-cc repository implements a robust job tracking and persistence system that ensures every code-generation request, review, or rescue operation is accounted for from start to finish. By combining an in-memory job registry with durable JSON state files, the plugin maintains continuity even when the companion process restarts.
Session-Based Job Identification
Every job executes within a session identified by the CODX_SESSION_ID environment variable, defined as the constant SESSION_ID_ENV in plugins/codex/scripts/lib/tracked-jobs.mjs. This UUID attaches to all requests sent to the Codex server, allowing the server to correlate messages with the correct session. The same identifier serves as the primary key within the local job registry.
The TrackedJobs Registry
The tracked-jobs.mjs module exports a trackedJobs Map that stores TrackedJob objects containing:
id: The session-wide job identifierstartTime: Epoch milliseconds when the job beganstatus: One of"running","finished", or"canceled"result: Optional payload returned upon completion
The module exposes trackJob(id) to register new jobs and finalizeJob(id, result) to update status and store return values. These functions mutate the shared Map, making the current state immediately available to all modules importing the registry.
Persistence Across Process Restarts
The Companion Process Architecture
The codex-companion.mjs script runs as a long-lived Node.js process that imports job-control.mjs to mediate between the user-facing interface and the background broker. This architecture separates job execution from the UI, ensuring that jobs survive temporary disconnections or companion restarts.
State Serialization with state.mjs
The state.mjs helper writes the trackedJobs registry to jobs-state.json on disk. When codex-companion.mjs starts, it reloads this file to re-hydrate active jobs, enabling crash recovery. If a job was running when the process terminated, the companion can either resume it or mark it as lost based on the persisted state.
Job Lifecycle Management and Cancellation
Graceful Shutdown Handling
When users execute codex cancel, the job-control.mjs module looks up the target job in the trackedJobs map, signals the broker to terminate the underlying process, and updates the status to "canceled". The cancellation propagates to the Codex server via the session ID, ensuring remote resources are released.
Integration with Lifecycle Hooks
Hook scripts like session-lifecycle-hook.mjs and stop-review-gate-hook.mjs import SESSION_ID_ENV from tracked-jobs.mjs to call trackJob and finalizeJob at workflow boundaries. This ensures that review gates and rescue operations are recorded consistently in the registry.
Accessing Job Status
The codex status command, implemented in plugins/codex/commands/status.md, reads the trackedJobs map to display active, completed, and failed jobs with timestamps and results. Because state persists to disk via state.mjs, this command functions correctly even after a companion restart.
Practical Implementation Examples
Starting and Tracking a New Job
// plugins/codex/scripts/lib/example-start.mjs
import { trackJob, finalizeJob } from "./tracked-jobs.mjs";
import { runJob } from "./job-control.mjs";
import { randomUUID } from "crypto";
async function startReviewJob(reviewPrompt) {
const jobId = randomUUID(); // Generate unique ID
trackJob(jobId); // Register in trackedJobs map
try {
const result = await runJob(jobId, reviewPrompt);
finalizeJob(jobId, result); // Mark as finished with payload
} catch (error) {
finalizeJob(jobId, { error: error.message });
throw error;
}
}
Completing a Job via Lifecycle Hook
// plugins/codex/scripts/stop-review-gate-hook.mjs
import { SESSION_ID_ENV, finalizeJob } from "./lib/tracked-jobs.mjs";
export async function onReviewComplete(context) {
const jobId = process.env[SESSION_ID_ENV];
if (jobId) {
finalizeJob(jobId, context.reviewResult);
}
}
Persisting and Restoring State
// plugins/codex/scripts/lib/state-operations.mjs
import { writeFileSync, readFileSync, existsSync } from "fs";
import { trackedJobs } from "./tracked-jobs.mjs";
const STATE_FILE = "jobs-state.json";
export function saveState() {
const entries = Array.from(trackedJobs.entries());
writeFileSync(STATE_FILE, JSON.stringify(entries), "utf8");
}
export function loadState() {
if (existsSync(STATE_FILE)) {
const entries = JSON.parse(readFileSync(STATE_FILE, "utf8"));
trackedJobs.clear();
for (const [id, job] of entries) {
trackedJobs.set(id, job);
}
}
}
Summary
- Session-based identification: The
CODX_SESSION_IDenvironment variable links all operations to a specific session defined intracked-jobs.mjs. - In-memory registry: The
trackedJobsMap maintains real-time job status, start times, and results. - JSON persistence: The
state.mjshelper serializes the registry tojobs-state.jsonfor crash recovery. - Process separation:
codex-companion.mjsandjob-control.mjsmanage execution independently of the UI. - Lifecycle hooks: Automated tracking via
trackJobandfinalizeJobensures no operations are lost acrossstop-review-gate-hook.mjsand other workflow boundaries.
Frequently Asked Questions
How does the Codex plugin recover jobs after a crash?
The companion process calls loadState() from state.mjs on startup, which reads jobs-state.json and repopulates the trackedJobs Map. Jobs that were running when the process terminated are reloaded and can be resumed or marked as failed based on the persisted timestamp and status.
Where is the job session ID stored?
The session identifier is stored in the CODX_SESSION_ID environment variable, defined as the constant SESSION_ID_ENV in plugins/codex/scripts/lib/tracked-jobs.mjs. This UUID is attached to every request sent to the Codex server and is accessible to all lifecycle hooks and the job control module.
Can I cancel a running job without losing state?
Yes. The codex cancel command uses job-control.mjs to signal the broker to terminate the process, then calls finalizeJob with status "canceled". This update is written to jobs-state.json via state.mjs, preserving the cancellation record even if the companion restarts.
What information is stored for each tracked job?
Each entry in the trackedJobs Map is a TrackedJob object containing the job's unique id, startTime (epoch milliseconds), current status (running/finished/canceled), and optional result payload containing return values or error messages.
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 →