# How Background Jobs Are Tracked and Managed in the Codex Plugin

> Discover how the Codex plugin tracks and manages background jobs using its file-based persistence layer and specialized modules for efficient workflow control.

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

---

**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`](https://github.com/openai/codex-plugin-cc/blob/main/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 path
- **`loadState`** and **`saveState`** – Atomic read/write operations for the global state file
- **`generateJobId`** – Creates unique identifiers for new jobs
- **`upsertJob`**, **`writeJobFile`**, and **`readJobFile`** – 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 jobs
- **`appendLogLine`** and **`appendLogBlock`** – Writes progress updates and final output to plain-text logs
- **`createProgressReporter`** – Returns a normalized event handler that writes to both the job log and optional `stderr`
- **`runTrackedJob`** – 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 jobs
- **`enrichJob`** – Adds computed metadata including elapsed time and phase inference via `inferLegacyJobPhase`
- **`filterJobsForCurrentSession`** – Isolates jobs belonging to the active `CODEX_COMPANION_SESSION_ID`
- **`resolveCancelableJob`** – 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:

```javascript
// 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 });
}

```

```javascript
// Report progress from inside the runner
import { createProgressReporter } from "./tracked-jobs.mjs";

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

```

```javascript
// 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);

```

```javascript
// 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), and `job-control.mjs` (queries)
- The `runTrackedJob` wrapper 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 `saveState` limits history to **50 jobs** via the `MAX_JOBS` constant
- Session isolation filters jobs by the `CODEX_COMPANION_SESSION_ID` environment 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`](https://github.com/openai/codex-plugin-cc/blob/main/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.