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 runTrackedJob in tracked-jobs.mjs, which coordinates initialization, progress tracking, and completion
  • Session isolation uses CODEX_COMPANION_SESSION_ID and filterJobsForCurrentSession in job-control.mjs to scope visibility
  • Automatic pruning limits storage to the latest 50 jobs through saveState in state.mjs
  • Progress reporting combines structured JSON updates with plain-text logs via createProgressReporter and appendLogLine

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:

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 →