How the Codex Plugin Captures, Stores, and Renders Job Outputs
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.
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 joboutput: A string accumulating all captured logsstatus: Enumeration ofrunning,succeeded, orfailedmetadata: 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.
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.
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.
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:
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.mjsintercepts stdout/stderr streams and buffers them in memory during job execution. - Store: The state singleton in
state.mjspersists 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 usingreview-output.schema.jsonfor display in the Codex UI. - Deliver:
codex-companion.mjstransmits 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →