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

> Discover how the Codex plugin expertly tracks and manages background jobs across sessions. Learn about its persistent storage and session isolation for seamless job continuity.

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

---

**The Codex plugin persists background job metadata and logs to a temporary directory structure (`$TMPDIR/codex-companion/state/`), enabling cross-session continuity while isolating jobs per Codex companion session via the `CODEX_COMPANION_SESSION_ID` environment variable.**

The openai/codex-plugin-cc repository implements a file-based state management system that tracks long-running operations like code reviews and repository analysis. Unlike in-memory solutions that vanish on process exit, this architecture stores job records as JSON files and human-readable logs outside the workspace, allowing developers to monitor, resume, and inspect background tasks across IDE restarts.

## File-Based State Architecture

The foundation of the job tracking system resides in `plugins/codex/scripts/lib/state.mjs`, which manages a dedicated state directory within the OS temporary folder. The `resolveStateDir` function establishes the path `$TMPDIR/codex-companion/state/` to house all runtime data, ensuring repository cleanliness and automatic cleanup by the operating system.

The storage layer maintains two critical structures:

- **Global state.json**: Tracks the index of recent jobs and metadata.
- **Per-job JSON files**: Individual records named by job ID containing status, timestamps, and session associations.

Key persistence functions in `state.mjs` include `loadState` and `saveState` for atomic reads and writes, while `upsertJob`, `writeJobFile`, and `readJobFile` handle individual job record CRUD operations. The system generates unique identifiers via `generateJobId`, which prefixes the job type (e.g., `"review"`) with entropy to prevent collisions.

## Job Lifecycle and Execution

When initiating background work, the `runTrackedJob` function in `plugins/codex/scripts/lib/tracked-jobs.mjs` orchestrates the complete lifecycle. The process begins with `createJobRecord`, which constructs a metadata object including `id`, `workspaceRoot`, `jobClass`, `kind`, `logFile`, and `createdAt` timestamps.

The following example demonstrates starting a review job:

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

```

The execution wrapper performs atomic operations: it writes an initial "running" record with `status: "running"`, `startedAt`, and process `pid`; invokes the user-supplied runner; and upon completion updates the job with final `status` (`completed` or `failed`), `completedAt`, result payload, and renders output to the log.

## Progress Reporting and Log Management

Real-time feedback flows through the `createProgressReporter` factory in `tracked-jobs.mjs`. This returns a reporter function that normalizes progress events and performs dual writes to both the log file (via `appendLogLine` or `appendLogBlock`) and optionally to `stderr`.

Developers report progress from inside the runner like this:

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

```

The logging system captures phase information alongside messages, creating a searchable record of execution flow. Log files reside alongside their corresponding JSON metadata in the state directory, enabling post-hoc debugging of complex background operations.

## Session Scoping and Isolation

The plugin supports multi-session isolation through the `CODEX_COMPANION_SESSION_ID` environment variable. When present, job records include this value in their `sessionId` field, allowing the UI to filter jobs to the current companion instance.

The `filterJobsForCurrentSession` function in `plugins/codex/scripts/lib/job-control.mjs` reads the environment variable via `getCurrentSessionId` and excludes jobs belonging to other sessions from status queries. This prevents session cross-contamination while preserving the ability to view historical jobs from the same session after reconnecting.

## Status Snapshots and Job Control

Higher-level UI commands rely on `plugins/codex/scripts/lib/job-control.mjs` for job introspection. The `buildStatusSnapshot` function aggregates running, recent, and latest-finished jobs, enriching each with elapsed time and phase information via `enrichJob` and `inferLegacyJobPhase`.

Retrieve a status snapshot for the current session:

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

```

The `enrichJob` helper augments raw records with human-friendly phase names and previews the last log lines. For targeted operations, `resolveCancelableJob` locates specific jobs by ID:

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

```

## Automatic Pruning and Storage Limits

To prevent unbounded growth, the `saveState` function in `state.mjs` enforces a retention policy defined by `MAX_JOBS` (defaulting to 50 entries). When persisting state updates, the system removes the oldest job files and associated logs once the threshold exceeds, ensuring predictable disk usage across long development cycles.

This pruning occurs transparently during regular state updates, requiring no manual intervention while maintaining recent history availability.

## Summary

- The Codex plugin stores background job state in `$TMPDIR/codex-companion/state/` as JSON files and plain-text logs, ensuring persistence across IDE sessions.
- **Core modules**: `state.mjs` handles persistence and IDs, `tracked-jobs.mjs` manages execution and logging, and `job-control.mjs` provides UI-facing status and control APIs.
- Jobs progress through distinct phases tracked via `upsertJob` updates, with `runTrackedJob` providing atomic lifecycle management from "running" to "completed" or "failed" states.
- Session isolation uses the `CODEX_COMPANION_SESSION_ID` environment variable, filtered by `filterJobsForCurrentSession` to present session-relevant jobs only.
- Automatic pruning limits historical data to `MAX_JOBS` (50) entries, preventing storage bloat while maintaining recent operational context.

## Frequently Asked Questions

### Where does the Codex plugin store background job data?

The plugin writes all job metadata and logs to a temporary directory typically located at `$TMPDIR/codex-companion/state/` (or equivalent OS temp path). This location holds a global [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) file alongside individual job JSON records and `.log` files, ensuring data persists outside the workspace directory and survives IDE restarts.

### How does the plugin isolate jobs between different Codex companion sessions?

Job isolation relies on the `CODEX_COMPANION_SESSION_ID` environment variable. When set, job records include this value as a `sessionId` field, and functions like `filterJobsForCurrentSession` in `job-control.mjs` automatically exclude jobs from other sessions when building status snapshots for UI commands like `/codex:status`.

### What happens when a background job completes or fails?

Upon runner completion, `runTrackedJob` in `tracked-jobs.mjs` updates the job JSON with final `status` (`completed` or `failed`), `completedAt` timestamp, result payload, and appends rendered output to the log file. The `saveState` function then enforces the `MAX_JOBS` limit (50) by pruning oldest entries, ensuring storage remains bounded while preserving recent history.

### How can developers report progress from within a running background job?

Developers obtain a progress reporter by calling `createProgressReporter` from `tracked-jobs.mjs`, passing options like `{ stderr: true, logFile }`. Invoking the returned function with a message and phase (e.g., `reporter({ message: "Analyzing...", phase: "analyzing" })`) writes structured updates to both the job log file and optionally to stderr for real-time monitoring.