How the Codex Plugin Tracks and Manages Background Jobs Across Sessions
The Codex plugin persists background job metadata, progress, and output to JSON files in the OS temporary directory, enabling cross-session tracking through file-based state management and session-specific filtering.
The openai/codex-plugin-cc repository implements a robust background job tracking system that survives process restarts and isolates work by session. By writing job state to the filesystem rather than keeping it in memory, the plugin ensures that background tasks remain visible and manageable even when the main process exits or reconnects. This architecture relies on three core modules: state.mjs, tracked-jobs.mjs, and job-control.mjs.
File-Based State Architecture
The plugin stores all runtime state outside the repository in a temporary directory to ensure separation between code and execution data.
Temporary Directory Structure
All job data lives in $TMPDIR/codex-companion/state/, where a global state.json tracks the job index and individual JSON files store per-job metadata. According to the source code in plugins/codex/scripts/lib/state.mjs, the resolveStateDir function establishes this path, while loadState and saveState handle persistence. Each job receives a unique ID via generateJobId, and the system maintains human-readable logs in parallel <jobId>.log files.
Job Record Management
Individual job records contain timestamps, status fields, optional session IDs, and file references. The upsertJob function in state.mjs writes these records atomically, while writeJobFile and readJobFile handle direct file I/O. This file-based approach ensures that background jobs remain durable across process lifecycles.
The Background Job Lifecycle
The plugin manages jobs through a strict lifecycle from initialization to pruning, implemented primarily in plugins/codex/scripts/lib/tracked-jobs.mjs.
Initialization and Log Creation
When a task begins, the system calls createJobRecord to generate metadata including createdAt and optional sessionId, followed by createJobLogFile to initialize an empty log file. These functions establish the foundational records necessary for tracking jobs across sessions.
Execution and Progress Reporting
The runTrackedJob function serves as the primary execution wrapper. It writes a "running" status entry containing startedAt and pid, then invokes the user-supplied runner. During execution, the createProgressReporter helper normalizes progress events, writes lines to the job log via appendLogLine or appendLogBlock, and updates the job JSON through upsertJob. Optional stderr echoing provides real-time visibility.
Completion and Pruning Strategy
Upon runner completion, runTrackedJob records the final status (completed or failed), timestamps, result payload, and appends rendered output to the log. The saveState function enforces a retention limit by keeping only the newest 50 jobs (MAX_JOBS), automatically removing older job files and logs to prevent unbounded growth.
Session Scoping and Isolation
To manage background jobs across sessions without cross-contamination, the plugin implements session-based filtering.
Environment-Based Session Identification
Jobs can be scoped to a specific Codex companion session through the CODEX_COMPANION_SESSION_ID environment variable. The getCurrentSessionId function in plugins/codex/scripts/lib/job-control.mjs reads this value, while filterJobsForCurrentSession restricts queries to jobs bearing the matching sessionId. This ensures that commands like /codex:status display only relevant work for the current context.
Status Aggregation
The buildStatusSnapshot function aggregates running, recent, and latest-finished jobs, enriching each with a phase guess via inferLegacyJobPhase and calculating elapsed time. For single-job queries, buildSingleJobSnapshot provides detailed views including log previews.
Working with the Job Tracking API
The following examples demonstrate how to interact with the job tracking system programmatically.
Starting a Tracked Job
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,
},
{}
);
const runner = async () => {
// …perform review, return { exitStatus, payload, rendered, threadId, turnId, summary }
};
return runTrackedJob(job, runner, { logFile });
}
Reporting Progress
import { createProgressReporter } from "./tracked-jobs.mjs";
const reporter = createProgressReporter({ stderr: true, logFile });
reporter({ message: "Searching repository...", phase: "investigating" });
Retrieving Session Status
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);
Canceling Active Jobs
import { resolveCancelableJob } from "./job-control.mjs";
async function cancelJob(jobId) {
const { workspaceRoot, job } = resolveCancelableJob(process.cwd(), jobId);
const { upsertJob } = await import("./state.mjs");
upsertJob(workspaceRoot, { id: job.id, status: "cancelled", completedAt: new Date().toISOString() });
}
Summary
- State persistence occurs in
$TMPDIR/codex-companion/state/via JSON files, ensuring data survives process exits - Job lifecycle management is handled by
runTrackedJobintracked-jobs.mjs, which coordinates initialization, progress tracking, and completion - Session isolation uses
CODEX_COMPANION_SESSION_IDandfilterJobsForCurrentSessioninjob-control.mjsto scope visibility - Automatic pruning limits storage to the latest 50 jobs through
saveStateinstate.mjs - Progress reporting combines structured JSON updates with plain-text logs via
createProgressReporterandappendLogLine
Frequently Asked Questions
Where does the Codex plugin store background job data?
The plugin writes all job metadata and logs to the OS temporary directory under $TMPDIR/codex-companion/state/. This includes a global state.json index and individual JSON files per job, along with plain-text .log files capturing human-readable output created by createJobLogFile.
How many jobs does the Codex plugin retain?
The system maintains a rolling window of the 50 most recent jobs (MAX_JOBS). The saveState function in state.mjs automatically prunes older entries and their associated log files during each write operation to prevent unbounded growth.
How does session isolation work for background jobs?
Jobs can be tagged with a sessionId derived from the CODEX_COMPANION_SESSION_ID environment variable. The filterJobsForCurrentSession function in job-control.mjs filters the global job list to return only those matching the current session, ensuring that UI commands like /codex:status show only relevant work.
Can I access job status after the original process exits?
Yes, because job state persists to the filesystem via writeJobFile and upsertJob in state.mjs, the data remains available after process termination. You can query this state using buildStatusSnapshot or buildSingleJobSnapshot from job-control.mjs when reconnecting to a workspace.
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 →