# How the Codex Plugin Tracks and Manages Background Jobs Across Sessions

>  Discover how the Codex plugin tracks and manages background jobs across sessions using transient JSON files for robust state management and efficient filtering.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: internals
- Published: 2026-07-17

---

**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`](https://github.com/openai/codex-plugin-cc/blob/main/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

```javascript
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

```javascript
import { createProgressReporter } from "./tracked-jobs.mjs";

const reporter = createProgressReporter({ stderr: true, logFile });
reporter({ message: "Searching repository...", phase: "investigating" });

```

### Retrieving Session Status

```javascript
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

```javascript
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`](https://github.com/openai/codex-plugin-cc/blob/main/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.