# How the Codex Plugin Handles Review Output Schemas for Structured Parsing

> Learn how the Codex plugin handles review output schemas for structured parsing with its JSON schema-based approach, ensuring machine-readable review outputs and typed results.

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

---

**The Codex plugin enforces machine-readable review outputs by loading a JSON schema from [`plugins/codex/schemas/review-output.schema.json`](https://github.com/openai/codex-plugin-cc/blob/main/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:

```javascript
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:

```javascript
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`:

```javascript
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`](https://github.com/openai/codex-plugin-cc/blob/main/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:

```json
{
  "$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:

```bash
/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:

```javascript
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`:

```javascript
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:

```javascript
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`](https://github.com/openai/codex-plugin-cc/blob/main/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.