How the Codex Plugin Handles Structured Output Parsing for Review Results
The Codex plugin enforces strict JSON schema validation to convert raw adversarial review text into typed objects with guaranteed fields like verdict, summary, and findings.
The openai/codex-plugin-cc repository implements a robust pipeline for structured output parsing for review results, ensuring that adversarial code reviews return machine-readable data instead of free-form text. By combining JSON Schema definitions with runtime validation, the plugin enables downstream tools to consume review findings without fragile string parsing.
The Four-Step Structured Output Pipeline
The plugin processes adversarial reviews through a deterministic four-stage pipeline defined in plugins/codex/scripts/codex-companion.mjs and supporting libraries.
1. Loading the JSON Schema Definition
At initialization, the plugin loads the strict type definition from plugins/codex/schemas/review-output.schema.json. This file mandates specific fields including verdict (enum: ["approve", "needs-attention"]), summary, findings (array of severity-annotated issues), and next_steps.
In codex-companion.mjs lines 15–68, the schema is imported via readOutputSchema:
const REVIEW_SCHEMA = path.join(ROOT_DIR, "schemas", "review-output.schema.json");
const schema = readOutputSchema(REVIEW_SCHEMA);
This schema object serves as the contract between the plugin and the Codex app-server.
2. Enforcing Schema Compliance via outputSchema
When executing an adversarial review, the plugin passes the loaded schema to runAppServerTurn using the outputSchema parameter (lines 415–416). This instructs the app-server to validate the model's final output against the schema before returning it:
const result = await runAppServerTurn(context.repoRoot, {
prompt,
model,
sandbox: "read-only",
outputSchema: readOutputSchema(REVIEW_SCHEMA) // Schema enforcement point
});
If the model can comply, the server returns a JSON-encoded string matching the schema exactly; otherwise, the request fails at the server level.
3. Safe JSON Parsing with parseStructuredOutput
After the turn completes, the raw text in result.finalMessage is processed by parseStructuredOutput exported from plugins/codex/scripts/lib/codex.mjs (lines 88–113). This utility attempts JSON.parse and returns a standardized result object:
const parsed = parseStructuredOutput(result.finalMessage, {
status: result.status,
failureMessage: result.error?.message ?? result.stderr
});
// Returns: { parsed: object|null, rawOutput: string, parseError: string|null }
On success, parsed.parsed contains the typed review object; on failure, parseError contains the exception message and rawOutput preserves the original string for debugging.
4. Propagating Typed Results to Downstream Consumers
The companion script stores the validated object in the job payload (lines 418–424 and 426–440), making it available to UI renderers and test suites:
return {
payload: {
result: parsed.parsed, // Structured object matching schema
rawOutput: parsed.rawOutput, // Original JSON string
parseError: parsed.parseError // Null when parsing succeeds
}
};
Downstream code in plugins/codex/scripts/lib/render.mjs (renderReviewResult) consumes this payload directly, accessing payload.result.verdict and payload.result.findings without secondary parsing.
Review Output Schema Structure
The schema defined in plugins/codex/schemas/review-output.schema.json requires four top-level properties:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["verdict", "summary", "findings", "next_steps"],
"properties": {
"verdict": {
"type": "string",
"enum": ["approve", "needs-attention"]
},
"summary": { "type": "string", "minLength": 1 },
"findings": {
"type": "array",
"items": {
"type": "object",
"required": [
"severity", "title", "body", "file",
"line_start", "line_end", "confidence", "recommendation"
],
"properties": {
"severity": {
"type": "string",
"enum": ["critical", "high", "medium", "low"]
},
"title": { "type": "string", "minLength": 1 },
"body": { "type": "string", "minLength": 1 },
"file": { "type": "string", "minLength": 1 },
"line_start": { "type": "integer", "minimum": 1 },
"line_end": { "type": "integer", "minimum": 1 },
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
"recommendation": { "type": "string" }
}
}
},
"next_steps": {
"type": "array",
"items": { "type": "string", "minLength": 1 }
}
}
}
Each finding includes precise location metadata (line_start, line_end) and a confidence score, enabling automated triage workflows.
End-to-End Implementation Flow
The complete integration from schema loading to result consumption follows this pattern:
import { readOutputSchema, parseStructuredOutput } from './lib/codex.mjs';
import { runAppServerTurn } from './codex-companion.mjs';
// 1. Load schema
const REVIEW_SCHEMA = path.join(ROOT_DIR, "schemas", "review-output.schema.json");
// 2. Execute review with structured output enforcement
const result = await runAppServerTurn(repoRoot, {
prompt: "Check for race conditions in the caching layer",
model: "gpt-5.4-mini",
sandbox: "read-only",
outputSchema: readOutputSchema(REVIEW_SCHEMA)
});
// 3. Parse structured output
const parsed = parseStructuredOutput(result.finalMessage, {
status: result.status,
failureMessage: result.error?.message
});
// 4. Access typed data
if (parsed.parsed) {
console.log(`Verdict: ${parsed.parsed.verdict}`);
console.log(`Findings: ${parsed.parsed.findings.length} issues detected`);
}
Testing and Validation
The test harness in tests/fake-codex-fixture.mjs validates that the plugin correctly transmits the schema. Lines 57–60 check for the presence of the verdict property in the outputSchema parameter:
const payload = message.params.outputSchema?.properties?.verdict
? structuredReviewPayload(prompt) // Returns mock JSON matching schema
: taskPayload(prompt, ...);
This ensures that tests accurately simulate the server behavior: when the schema is present, the fixture returns structured JSON; otherwise, it returns plain text. The tight coupling between the runtime schema transmission and test validation prevents regressions in structured output parsing for review results.
Programmatic Usage Examples
Invoking Adversarial Reviews from Claude Code
Trigger a structured review via the CLI command:
/codex:adversarial-review --base main \
"Analyze thread-safety in the connection pool implementation"
This command invokes executeReviewRun with reviewName: "Adversarial Review", which automatically loads the JSON schema and parses the structured result according to the four-step pipeline.
Accessing Parsed Results in JavaScript
Consume review results programmatically without manual JSON parsing:
const reviewJob = await codex.runAdversarialReview({
cwd: repoRoot,
base: "main",
focusText: "thread-safety",
model: "gpt-5.4-mini"
});
if (reviewJob.payload.result) {
console.log("Verdict:", reviewJob.payload.result.verdict);
console.log("Summary:", reviewJob.payload.result.summary);
// Iterate over typed findings
reviewJob.payload.result.findings.forEach(finding => {
console.log(`[${finding.severity}] ${finding.title} (${finding.file}:${finding.line_start})`);
});
}
The payload.result object is fully typed and guaranteed to contain the fields defined in review-output.schema.json.
Customizing Output Schemas
For specialized review types, create a custom schema file and modify codex-companion.mjs:
const SECURITY_SCHEMA = path.join(ROOT_DIR, "schemas", "security-review.schema.json");
const result = await runAppServerTurn(context.repoRoot, {
prompt,
model,
sandbox: "read-only",
outputSchema: readOutputSchema(SECURITY_SCHEMA) // Custom schema injection
});
The existing parseStructuredOutput utility handles custom schemas transparently, attempting to parse the returned JSON and surfacing errors via the parseError field if validation fails.
Summary
- Schema-driven contracts: The plugin loads
review-output.schema.jsonto define mandatory fields (verdict,findings,next_steps) for all adversarial reviews. - Server-side enforcement: Passing
outputSchematorunAppServerTurncompels the Codex app-server to return JSON matching the schema or fail explicitly. - Safe parsing layer:
parseStructuredOutputinlib/codex.mjswrapsJSON.parseto return either a typed object or a detailed error without throwing. - Typed consumption: Downstream code receives
payload.resultas a validated object, eliminating the need for ad-hoc regex parsing or string manipulation. - Test parity: The fixture in
tests/fake-codex-fixture.mjsmirrors runtime behavior by detecting the schema'sverdictproperty to return appropriate mock data.
Frequently Asked Questions
What happens if the model returns invalid JSON?
The parseStructuredOutput function catches parsing exceptions and returns an object where parsed is null, parseError contains the error message, and rawOutput preserves the original string. This allows the plugin to gracefully handle model hallucinations or schema violations without crashing the review pipeline.
Can I modify the review output schema?
Yes. You can create a new JSON schema file and pass it to runAppServerTurn via the outputSchema parameter. However, downstream components like renderReviewResult expect the standard fields (verdict, findings, etc.), so custom schemas require corresponding updates to the rendering logic.
How does the Codex app-server enforce the schema?
When the outputSchema option is provided in runAppServerTurn, the app-server validates the model's final turn output against the schema before returning it to the plugin. If the model cannot produce valid JSON conforming to the schema, the server returns an error status instead of malformed text.
Where is the parsed review result stored?
After parsing, the structured object is stored in reviewJob.payload.result (lines 426–440 in codex-companion.mjs). This location is consistent across adversarial reviews and is accessed by both the CLI renderer in lib/render.mjs and external consumers of the plugin API.
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 →