# How the Codex Plugin Captures, Stores, and Renders Job Outputs

> Discover how the Codex plugin captures, stores, and renders job outputs. Learn about stdout/stderr interception, log persistence, and UI-ready JSON transformation by the openai/codex-plugin-cc runtime.

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

---

**The openai/codex-plugin-cc runtime intercepts stdout/stderr through the TrackedJobs helper, persists accumulated logs in a singleton state module, and transforms them into UI-ready JSON via the render library for display in the Codex chat interface.**

The openai/codex-plugin-cc repository orchestrates background jobs—such as code generation runs, reviews, or rescue operations—through a dedicated runtime library. Understanding how the Codex plugin manages job outputs requires examining three distinct phases: real-time stream capture via **TrackedJobs**, centralized persistence in the **state** singleton, and schema-driven rendering through the **render** module.

## Capturing Real-Time Job Output Streams

### Intercepting stdout and stderr with TrackedJobs

When a job initiates via `plugins/codex/scripts/lib/job-control.mjs`, the runtime registers a tracked job object. The **TrackedJobs** helper in `plugins/codex/scripts/lib/tracked-jobs.mjs` intercepts every write to stdout and stderr, pushing each chunk into an in-memory buffer attached to the job’s state.

```javascript
import { startJob } from "./lib/job-control.mjs";
import { captureOutput } from "./lib/tracked-jobs.mjs";

// Initialize a background review job
const job = await startJob({
  command: "codex review",
  args: ["--path", "./src"],
});

// Redirect process streams through the capture helper
process.stdout.write = captureOutput(job.id, "stdout");
process.stderr.write = captureOutput(job.id, "stderr");

```

This interception ensures that no output is lost between the process execution and the persistence layer, even for long-running operations.

## Storing Job State and Output in a Singleton

### The Global State Module and Job Records

Captured output is persisted in the **global state** object managed by `plugins/codex/scripts/lib/state.mjs`. This module maintains a Map of job IDs to `JobRecord` objects, each containing:

- `id`: The unique identifier for the job
- `output`: A string accumulating all captured logs
- `status`: Enumeration of `running`, `succeeded`, or `failed`
- `metadata`: Optional fields including timestamps and exit codes

When the job completes, `job-control.mjs` finalizes the record by marking the status and flushing any remaining buffers. Because `state.mjs` operates as a singleton module, any part of the plugin—including UI-side code—can query the latest job results synchronously.

```javascript
import { getJobRecord } from "./lib/state.mjs";

// Retrieve the completed job data
const finalRecord = getJobRecord(job.id);
console.log("Final output:", finalRecord.output);
console.log("Exit status:", finalRecord.status);

```

## Rendering Output for the Codex UI

### Transforming Logs into Structured UI Components

The **render** library in `plugins/codex/scripts/lib/render.mjs` converts raw output strings into rich UI representations suitable for the Codex chat interface. It parses content into discrete **blocks**—such as code fences, markdown sections, and error highlights—and validates them against the **Codex UI schema** defined in [`plugins/codex/schemas/review-output.schema.json`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/schemas/review-output.schema.json).

The `codex-companion.mjs` script emits the resulting JSON payload over a WebSocket to the Codex host, where it renders inside the chat view. The system supports **incremental updates**: as new chunks arrive from running jobs, `render.mjs` regenerates the payload and pushes it live via the companion’s WebSocket, providing users with real-time feedback.

```javascript
import { renderJobOutput } from "./lib/render.mjs";

// Generate the UI payload from stored state
const uiPayload = renderJobOutput(finalRecord);
await sendToCompanion(uiPayload); // Delivers JSON over WebSocket to Codex host

```

### Handling On-Demand Output Retrieval

For client commands such as `/codex result <id>`, the plugin queries the state directly and renders the output on demand:

```javascript
import { getJobRecord } from "./lib/state.mjs";
import { renderJobOutput } from "./lib/render.mjs";

export async function resultCommand(jobId) {
  const rec = getJobRecord(jobId);
  if (!rec) return "Job not found.";
  return renderJobOutput(rec); // Returns formatted markdown block
}

```

## Summary

- **Capture**: The TrackedJobs helper in `tracked-jobs.mjs` intercepts stdout/stderr streams and buffers them in memory during job execution.
- **Store**: The state singleton in `state.mjs` persists job records (`JobRecord`) containing accumulated output, status, and metadata for all completed and running jobs.
- **Render**: The render library (`render.mjs`) transforms raw output strings into structured JSON blocks using [`review-output.schema.json`](https://github.com/openai/codex-plugin-cc/blob/main/review-output.schema.json) for display in the Codex UI.
- **Deliver**: `codex-companion.mjs` transmits rendered payloads over WebSocket, supporting incremental updates for live job monitoring.

## Frequently Asked Questions

### How does the Codex plugin handle real-time output updates while a job is running?

The plugin supports incremental rendering through the `render.mjs` module. As the TrackedJobs helper captures new stdout/stderr chunks, `job-control.mjs` triggers re-rendering of the output payload. The `codex-companion.mjs` script then pushes the updated JSON via WebSocket to the Codex host, allowing users to see live progress without waiting for job completion.

### Where is job output physically stored in the codex-plugin-cc architecture?

Output is stored in-memory within the **global state** singleton defined in `plugins/codex/scripts/lib/state.mjs`. Each job receives a `JobRecord` object that accumulates output strings in its `output` property. This design prioritizes low-latency access for the companion script and assumes ephemeral execution contexts rather than persistent disk storage.

### What schema defines the UI structure for rendered job outputs?

The **Codex UI schema** located at [`plugins/codex/schemas/review-output.schema.json`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/schemas/review-output.schema.json) defines the JSON structure that the render library must produce. This schema specifies how code blocks, markdown sections, and error highlights map to UI components, ensuring consistent presentation across different job types in the Codex chat interface.

### How can I retrieve the output of a completed job programmatically?

Use the `getJobRecord` function exported from `plugins/codex/scripts/lib/state.mjs`, passing the job ID as an argument. This returns the complete `JobRecord` including the accumulated `output` string and `status` field. Pass this record to `renderJobOutput` from `render.mjs` if you need the formatted UI representation rather than the raw log text.