How Background Jobs Are Tracked and Managed in the Codex Plugin
The Codex plugin implements a lightweight, file-based persistence layer in the OS temporary directory to track job metadata, progress streams, and execution state through three specialized modules: state.mjs, tracked-jobs.mjs, and job-control.mjs.
The openai/codex-plugin-cc repository relies on this robust background job management system to maintain state outside the workspace. Understanding how background jobs are tracked and managed in the Codex plugin requires examining the JSON-based storage architecture, the execution wrapper lifecycle, and the session-scoping mechanisms that isolate concurrent operations.
Core Architecture Components
State Persistence Layer (state.mjs)
Located at plugins/codex/scripts/lib/state.mjs, this module manages the $TMPDIR/codex-companion/state/ directory. It maintains a global state.json alongside individual job JSON files containing unique IDs, timestamps, status flags, and optional session identifiers.
Key functions include:
resolveStateDir– Computes the platform-specific temporary directory pathloadStateandsaveState– Atomic read/write operations for the global state filegenerateJobId– Creates unique identifiers for new jobsupsertJob,writeJobFile, andreadJobFile– CRUD operations for individual job records
Job Execution and Logging (tracked-jobs.mjs)
The plugins/codex/scripts/lib/tracked-jobs.mjs module provides the execution framework that binds runtime processes to persistent state. It creates human-readable log files (<jobId>.log) and manages progress streams through specialized reporter factories.
Core responsibilities include:
createJobLogFile– Initializes empty log files for new jobsappendLogLineandappendLogBlock– Writes progress updates and final output to plain-text logscreateProgressReporter– Returns a normalized event handler that writes to both the job log and optionalstderrrunTrackedJob– The main execution wrapper that manages the transition from "running" to "completed" or "failed"
Status Aggregation (job-control.mjs)
Higher-level query APIs reside in plugins/codex/scripts/lib/job-control.mjs. This module aggregates dispersed job files into coherent snapshots for UI commands like /codex:status and /codex:cancel.
Key capabilities include:
buildStatusSnapshot– Compiles lists of running, recent, and latest-finished jobsenrichJob– Adds computed metadata including elapsed time and phase inference viainferLegacyJobPhasefilterJobsForCurrentSession– Isolates jobs belonging to the activeCODEX_COMPANION_SESSION_IDresolveCancelableJob– Locates active jobs by ID for termination signals
The Job Lifecycle
1. Creation and Initialization
When a background task initiates, the system calls createJobRecord to generate metadata including createdAt, jobClass, kind, and optional sessionId. The createJobLogFile function initializes an empty log file, while writeJobFile persists the initial JSON record to the temporary directory.
2. Running State and Process Binding
The runTrackedJob function marks the job as active by setting status: "running", capturing the startedAt timestamp, and recording the process ID (pid). This state is immediately persisted via upsertJob, ensuring the system can track orphans and support cancellation signals.
3. Progress Reporting
During execution, the runner receives a reporter instance from createProgressReporter. When invoked with progress events, this reporter normalizes the data, appends human-readable lines to the log via appendLogLine, and updates the job JSON through upsertJob to reflect current phase and timestamps.
4. Completion and Result Persistence
Upon runner resolution, runTrackedJob finalizes the record with status: "completed" or "failed", captures completedAt, and stores the result payload and rendered output. The final output block is appended to the log file via appendLogBlock, completing the audit trail.
5. Automatic Pruning
The saveState function enforces a MAX_JOBS limit of 50 entries. During persistence, it automatically removes older job files and associated logs from the temporary directory, preventing unbounded storage growth while retaining recent history.
Session Scoping and Isolation
Jobs can be scoped to specific Codex companion sessions using the CODEX_COMPANION_SESSION_ID environment variable. The getCurrentSessionId helper reads this value from the environment, while filterJobsForCurrentSession ensures that status queries only return jobs belonging to the current workspace context. This isolation prevents cross-contamination between unrelated operations.
Implementation Examples
The following patterns demonstrate how to interact with the job tracking system:
// Start a tracked job (e.g., a review task)
import { generateJobId, createJobRecord, createJobLogFile, runTrackedJob } from "./tracked-jobs.mjs";
async function startReviewJob(workspaceRoot, reviewTask) {
const jobId = generateJobId("review");
const logFile = createJobLogFile(workspaceRoot, jobId, "Codex Review");
const job = createJobRecord(
{
id: jobId,
workspaceRoot,
jobClass: "review",
kind: "review",
logFile,
},
{}
);
// The runner does the actual work and returns an execution object
const runner = async () => {
// …perform review, return { exitStatus, payload, rendered, threadId, turnId, summary }
};
return runTrackedJob(job, runner, { logFile });
}
// Report progress from inside the runner
import { createProgressReporter } from "./tracked-jobs.mjs";
const reporter = createProgressReporter({ stderr: true, logFile });
reporter({ message: "Searching repository...", phase: "investigating" });
// Retrieve a concise status snapshot for the current session
import { buildStatusSnapshot } from "./job-control.mjs";
const snapshot = buildStatusSnapshot(process.cwd(), { env: process.env });
console.log("Running jobs:", snapshot.running);
console.log("Recent jobs:", snapshot.recent);
// Cancel an active job by ID
import { resolveCancelableJob } from "./job-control.mjs";
async function cancelJob(jobId) {
const { workspaceRoot, job } = resolveCancelableJob(process.cwd(), jobId);
// Here you could send a SIGTERM to job.pid or simply mark it cancelled
// For demonstration, we just update the state:
const { upsertJob } = await import("./state.mjs");
upsertJob(workspaceRoot, { id: job.id, status: "cancelled", completedAt: new Date().toISOString() });
}
Summary
- The Codex plugin stores all job state in
$TMPDIR/codex-companion/state/using JSON metadata files and plain-text logs - Three modules coordinate persistence:
state.mjs(storage),tracked-jobs.mjs(execution), andjob-control.mjs(queries) - The
runTrackedJobwrapper manages the complete lifecycle from creation through completion, capturing process IDs and final results - Progress reporters normalize events and write to both machine-readable JSON and human-readable log files
- Automatic pruning enforced by
saveStatelimits history to 50 jobs via theMAX_JOBSconstant - Session isolation filters jobs by the
CODEX_COMPANION_SESSION_IDenvironment variable to maintain workspace separation
Frequently Asked Questions
Where does the Codex plugin store background job data?
All job metadata and logs are stored in the OS temporary directory under $TMPDIR/codex-companion/state/. This location holds the global state.json file, individual job JSON files containing status and timestamps, and <jobId>.log files with human-readable progress output. This design ensures runtime state never contaminates the workspace repository.
How does the plugin handle job progress updates?
The createProgressReporter function in tracked-jobs.mjs returns a reporter that normalizes progress events into structured entries. When called, it writes to the job's log file via appendLogLine and simultaneously updates the job's JSON record through upsertJob. This dual-write approach maintains both real-time log streams and queryable metadata.
What is the maximum number of jobs retained by the system?
The plugin maintains a sliding window of 50 jobs defined by the MAX_JOBS constant in state.mjs. When saveState executes, it prunes older job files and associated logs from the temporary directory, ensuring the storage footprint remains bounded while preserving recent operational history.
How are jobs isolated between different Codex sessions?
Jobs can be scoped to specific sessions via the CODEX_COMPANION_SESSION_ID environment variable. The filterJobsForCurrentSession function in job-control.mjs filters the job list to include only entries matching the current session ID. This mechanism ensures that /codex:status commands in one session do not display jobs from unrelated workspace contexts.
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 →