# How the Codex Plugin Reports Progress on Long-Running Tasks

> Discover how the Codex plugin reports progress on long running tasks using an event-driven system that logs, updates metadata, and provides live user previews.

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

---

**The Codex plugin implements an event-driven progress tracking subsystem that normalizes progress events, writes to job-specific logs, patches persisted job metadata in real-time, and renders live progress previews for user monitoring.**

The `openai/codex‑plugin-cc` repository provides deep context awareness for AI-assisted coding workflows. When you're working with long-running operations like automated code reviews (`codex:review`) or background tasks (`codex:task`), understanding how the plugin handles **progress reporting for long‑running tasks** lets you monitor execution, debug failures, and manage concurrent jobs effectively.

## The Progress Reporter Architecture

Progress tracking centers on three coordinated components in `plugins/codex/scripts/lib/tracked-jobs.mjs`:

- **`createTrackedProgress`** – Factory that initializes logging and progress hooks for a job
- **`normalizeProgressEvent`** – Transforms raw inputs into structured event objects
- **`createJobProgressUpdater`** – Persists phase changes to the job's JSON metadata

When a command starts, the companion immediately constructs a reporter:

```js
const { logFile, progress } = createTrackedProgress(job, {
  logFile: options.logFile,
  stderr: !options.json
});

```

The `progress` function returned accepts either a **string message** or a **structured progress event** with `message`, `phase`, `threadId`, and `turnId` properties.

## Event Normalization and Persistent Logging

Every progress call flows through `normalizeProgressEvent` in `tracked-jobs.mjs`. The reporter:

1. **Appends to the job log** via `appendLogLine` or `appendLogBlock` for multi-line output
2. **Optionally forwards** to the job-progress updater when state changes occur

This ensures every operation leaves an auditable trail in `~/.codex/jobs/{jobId}/log.txt` while keeping structured metadata synchronized.

The log acts as the **source of truth**; the JSON job record only stores the *latest* phase, thread, and turn identifiers for quick UI access.

## Live Metadata Updates and Phase Tracking

The `createJobProgressUpdater` function in `tracked-jobs.mjs` maintains the live job state:

- Stores the last seen `phase`, `threadId`, and `turnId`
- Compares incoming events against cached values
- Calls `upsertJob` / `writeJobFile` to patch the persisted record when differences are detected

This design optimizes for **write efficiency**—metadata only flushes to disk when meaningful state transitions occur, not on every log line.

### Legacy Phase Inference

For jobs created before structured progress events, the plugin falls back to `inferLegacyJobPhase` in `plugins/codex/scripts/lib/job-control.mjs`. This scans the most recent log lines and maps keywords to **human-readable phases**:

- "starting" → initialization
- "reviewing", "investigating", "verifying" → analysis stages
- "editing" → modification phase
- "finalizing" → completion

## Enriching and Rendering Progress for Users

The `enrichJob` function in `job-control.mjs` prepares job data for display by:

- Merging persisted records with derived `phase`
- Calculating formatted elapsed time
- Extracting a trimmed `progressPreview` (last *N* lines from the log)

Finally, `renderJobStatusReport` in `plugins/codex/scripts/lib/render.mjs` formats the output:

```js
if (job.progressPreview?.length) {
  lines.push("  Progress:");
  for (const line of job.progressPreview) {
    lines.push(`    ${line}`);
  }
}

```

This produces the familiar **"Progress:"** section users see when running `/codex:status`.

## Practical Usage: From Command Start to Status Check

### Starting a Tracked Command

In `plugins/codex/scripts/codex-companion.mjs`, foreground commands follow this pattern:

```js
async function runForegroundCommand(job, runner, options = {}) {
  const { logFile, progress } = createTrackedProgress(job, {
    logFile: options.logFile,
    stderr: !options.json
  });
  
  const execution = await runTrackedJob(
    job, 
    () => runner(progress), 
    { logFile }
  );
  
  outputResult(
    options.json ? execution.payload : execution.rendered, 
    options.json
  );
}

```

The `runner` function receives the `progress` callback and emits updates throughout execution.

### Emitting Progress from Task Implementations

Long-running sub-processes report granular state:

```js
async function heavyTask(progress) {
  progress("Fetching repository…");
  await fetchRepo();
  
  progress({ message: "Running analysis", phase: "investigating" });
  const result = await analyze();
  
  progress({ message: "Done", phase: "done" });
  return result;
}

```

### Monitoring Progress

Users interact with the system through simple CLI commands:

```bash

# Start background review

codex review --wait

# → "Codex review started in the background as a1b2c3. 

#    Check /codex:status a1b2c3 for progress."

# Inspect live progress

codex status a1b2c3

```

The status output includes the **Progress:** preview drawn directly from the job log, with no additional polling logic required in sub-agents.

## Key Files and Responsibilities

| File | Core Responsibility |
|------|---------------------|
| `plugins/codex/scripts/lib/tracked-jobs.mjs` | Job creation, progress reporter factory, log appenders, metadata persistence |
| `plugins/codex/scripts/lib/job-control.mjs` | Phase inference from logs, job enrichment, elapsed time formatting |
| `plugins/codex/scripts/lib/render.mjs` | Terminal-friendly status report generation including progress previews |
| `plugins/codex/scripts/codex-companion.mjs` | Command orchestration, wiring progress reporters into runners |

## Summary

- **Progress reporters** are created per-job via `createTrackedProgress` and accept both string and structured event inputs
- **Dual-write strategy**: every progress event appends to a persistent log *and* conditionally updates JSON metadata
- **Phase tracking** combines explicit event fields with fallback log-scanning for legacy compatibility
- **Zero-polling UX**: users check `/codex:status` to see live previews derived from the authoritative log

## Frequently Asked Questions

### What happens if a progress event lacks a phase field?

The reporter still logs the message via `appendLogLine`. The `createJobProgressUpdater` only triggers a metadata write when `phase`, `threadId`, or `turnId` change—missing fields simply skip that particular update path. Legacy jobs later undergo phase inference via `inferLegacyJobPhase` when rendered.

### Can I disable stderr progress output while keeping file logging?

Yes. Pass `stderr: false` in the options to `createTrackedProgress`. The log file continues receiving all events via `appendLogLine`, but nothing writes to the terminal. This configuration suits JSON-mode consumers that parse structured output instead of human-readable progress.

### How large does the progress preview grow?

The `progressPreview` is intentionally trimmed to the last *N* lines (implementation in `enrichJob`). This prevents unbounded memory growth for marathon tasks while preserving recent context. The full history remains available in the job's log file.

### Does the plugin support concurrent progress streams from multiple threads?

Yes. The progress event schema includes `threadId` and `turnId` fields. The `createJobProgressUpdater` tracks these identifiers and persists changes, enabling the UI to distinguish between parallel execution contexts within a single job.