How the Codex Plugin Handles Review Output Schemas for Structured Parsing

The Codex plugin enforces machine-readable review outputs by loading a JSON schema from plugins/codex/schemas/review-output.schema.json, passing it to the app-server via the outputSchema parameter in runAppServerTurn, and parsing the returned JSON string using parseStructuredOutput to produce a typed result object.

The openai/codex-plugin-cc repository implements a strict structured parsing pipeline for adversarial reviews. By defining a rigid JSON schema, the plugin ensures that LLM responses conform to a predictable format with typed fields like verdict, summary, and findings, eliminating the need for fragile regex extraction or ad-hoc text parsing.

The Four-Step Structured Parsing Pipeline

The plugin processes structured review outputs through four distinct phases, each implemented in specific source files.

Step 1: Load the JSON Schema

On initialization, the companion script reads the schema definition from disk. In plugins/codex/scripts/codex-companion.mjs at line 68, the plugin defines the schema path:

const REVIEW_SCHEMA = path.join(ROOT_DIR, "schemas", "review-output.schema.json");

Immediately after, lines 15‑16 invoke readOutputSchema(REVIEW_SCHEMA), a utility from plugins/codex/scripts/lib/codex.mjs that reads and caches the JSON file for reuse across review invocations.

Step 2: Pass the Schema to the App-Server

When executing an adversarial review, the plugin instructs the Codex app-server to constrain its output using the outputSchema option. At lines 415‑416 in codex-companion.mjs, the schema is injected into the turn configuration:

const result = await runAppServerTurn(context.repoRoot, {
  prompt,
  model,
  sandbox: "read-only",
  outputSchema: readOutputSchema(REVIEW_SCHEMA)  // Schema enforcement happens here
});

The server validates the model's response against this schema before returning it, ensuring that only valid JSON Structures reach the client.

Step 3: Parse the Raw JSON String

After the turn completes, the raw text in result.finalMessage must be converted from a string to a JavaScript object. The parseStructuredOutput function (lines 88‑113 in plugins/codex/scripts/lib/codex.mjs) handles this safely:

  • Attempts JSON.parse on the input string
  • Returns { parsed: object, parseError: null } on success
  • Returns { parsed: null, parseError: message, rawOutput: originalText } on failure

This defensive parsing prevents downstream crashes from malformed model outputs while preserving the original text for debugging.

Step 4: Propagate the Parsed Object

Finally, the plugin stores the structured data in the job payload for downstream consumers. Lines 418‑424 in codex-companion.mjs extract the parsed object, and lines 426‑440 assign it to payload.result:

const parsed = parseStructuredOutput(result.finalMessage, {
  status: result.status,
  failureMessage: result.error?.message ?? result.stderr
});

return {
  payload: {
    // ...other fields...
    result: parsed.parsed,        // Structured object matching the schema
    rawOutput: parsed.rawOutput,  // Original JSON string
    parseError: parsed.parseError // null if validation succeeded
  }
};

The renderReviewResult function in plugins/codex/scripts/lib/render.mjs subsequently consumes this payload to format CLI output, while tests and UI components can rely on the typed fields without additional parsing.

Review Output Schema Structure

The schema defined in plugins/codex/schemas/review-output.schema.json requires four top-level properties: verdict, summary, findings, and next_steps. Each finding must include severity, location, and confidence metrics:

{
  "$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 }
    }
  }
}

This schema guarantees that every review output contains actionable metadata with precise line references and severity classifications.

Implementation Examples

Running an Adversarial Review via CLI

Invoke the structured review from Claude Code using the adversarial review command:

/codex:adversarial-review --base main \
  "Check for potential race conditions in the new caching layer"

This command triggers executeReviewRun with reviewName: "Adversarial Review", automatically enabling the schema pipeline described above.

Accessing Parsed Results Programmatically

Consume the structured output in your own tools by inspecting the payload.result object:

const reviewJob = await codex.runAdversarialReview({
  cwd: repoRoot,
  base: "main",
  focusText: "race conditions",
  model: "gpt-5.4-mini"
});

if (reviewJob.payload.result) {
  console.log("Verdict:", reviewJob.payload.result.verdict);
  console.log("Summary:", reviewJob.payload.result.summary);
  console.table(reviewJob.payload.result.findings);
}

Because the result conforms to the JSON schema, you can safely destructure fields like severity, line_start, and confidence without null checks on the parsing operation itself.

Extending with Custom Schemas

For specialized review types, create a new schema file and modify codex-companion.mjs:

const SECURITY_SCHEMA = path.join(ROOT_DIR, "schemas", "security-review.schema.json");

await runAppServerTurn(context.repoRoot, {
  prompt: securityPrompt,
  model,
  sandbox: "read-only",
  outputSchema: readOutputSchema(SECURITY_SCHEMA)  // Custom schema injection
});

The existing parseStructuredOutput utility handles validation and error reporting without modification.

Testing Schema Validation

The test harness in tests/fake-codex-fixture.mjs validates that the plugin correctly transmits the schema. At lines 57‑60, the mock server checks for the presence of the verdict property in the outputSchema to determine response behavior:

const payload = message.params.outputSchema && 
                message.params.outputSchema.properties && 
                message.params.outputSchema.properties.verdict
  ? structuredReviewPayload(prompt)   // Returns JSON matching the schema
  : taskPayload(prompt, ...);

This confirms that the runtime behavior and test fixtures remain synchronized: when the plugin sends the schema, the mock returns structured data; otherwise, it falls back to plain text.

Summary

  • Schema Loading: The plugin reads review-output.schema.json at startup using readOutputSchema to cache the validation rules.
  • Server Enforcement: The outputSchema parameter in runAppServerTurn forces the Codex app-server to return JSON matching the schema or fail validation.
  • Safe Parsing: parseStructuredOutput in codex.mjs handles JSON.parse with error isolation, returning either a typed object or detailed error metadata.
  • Payload Integration: Parsed results propagate through payload.result, enabling type-safe consumption by renderers and downstream tools.
  • Test Coverage: The fake fixture validates schema transmission by inspecting the verdict property, ensuring end-to-end contract adherence.

Frequently Asked Questions

How does the Codex plugin ensure the model outputs valid JSON?

The plugin passes the JSON schema to the Codex app-server via the outputSchema option in runAppServerTurn. The server layer validates the model's generated text against this schema before returning it to the client, rejecting outputs that violate type constraints or required field rules.

What happens if the model returns malformed JSON?

The parseStructuredOutput function wraps JSON.parse in a try-catch block. On failure, it returns the original raw text in rawOutput along with a parseError string, allowing the plugin to display the unparseable response to the user while preventing downstream code from crashing on undefined properties.

Can I modify the review output schema for custom workflows?

Yes. Create a new JSON schema file in plugins/codex/schemas/ and pass it to runAppServerTurn using readOutputSchema(). The parsing logic remains identical, though you must ensure downstream consumers like renderReviewResult can handle your custom field structures.

Where is the structured review data stored after parsing?

The parsed object is stored in payload.result of the returned job object (lines 426‑440 in codex-companion.mjs). The original JSON string is preserved in payload.rawOutput, and any parsing errors are captured in payload.parseError for diagnostic purposes.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →