# How the OpenAI Codex Plugin Review Gate Works and What Happens When It Blocks a Stop

> Understand the OpenAI Codex plugin review gate, its safety functions, and the consequences of a blocked stop. Learn how it automatically reviews changes before session termination.

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

---

**The OpenAI Codex plugin review gate is a safety layer that automatically reviews changes from the previous Claude turn before allowing a session to stop, emitting a JSON block decision with a human-readable reason when issues are detected.**

The OpenAI Codex plugin review gate acts as a final checkpoint in Claude sessions, intercepting stop commands to ensure no unreviewed code modifications are left behind. Located in the `openai/codex-plugin-cc` repository, this mechanism runs a Codex-driven analysis of your most recent changes and decides whether the session may terminate or must remain active for fixes.

## Hook Entry Point and Input Processing

The review gate activates through the script [`stop-review-gate-hook.mjs`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/stop-review-gate-hook.mjs), which serves as the main entry point.

The hook begins by reading JSON input from STDIN via `readHookInput`, capturing critical session context including the workspace directory, session ID, and the previous Claude response. It then resolves the workspace root, loads the plugin configuration using `getConfig`, and gathers currently queued or running Codex jobs through `listJobs` and `filterJobsForCurrentSession`.

## Configuration and State Management

The gate respects the `stopReviewGate` boolean flag stored in [`plugins/codex/scripts/lib/state.mjs`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/state.mjs).

- **If `stopReviewGate` is false**: The gate logs any running job notes to STDERR via `logNote` and exits without performing a review.
- **If `stopReviewGate` is true**: The gate proceeds to validate setup and execute the review.

According to the source code, the default state initializes `stopReviewGate: false` (lines 19‑25 in `state.mjs`), meaning the gate is opt-in by default.

## Setup Validation

Before launching any review tasks, the hook verifies that Codex is properly configured for the current workspace using `buildSetupNote`. If Codex setup is incomplete, the gate logs a helpful message to STDERR and aborts, allowing the stop command to proceed without review.

## Prompt Construction and Template Injection

When validation passes, the gate constructs the review prompt using the template [[`stop-review-gate.md`](https://github.com/openai/codex-plugin-cc/blob/main/stop-review-gate.md)](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/prompts/stop-review-gate.md).

The template system injects the previous Claude response through `interpolateTemplate`, replacing the `CLAUDE_RESPONSE_BLOCK` placeholder. The resulting instructions direct Codex to **only review the work from the previous Claude turn** and return a **compact** answer beginning with either `ALLOW:` or `BLOCK:`.

## Executing the Review Task

The hook delegates the actual review to [`codex-companion.mjs`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/codex-companion.mjs), spawning it as a synchronous child process with a 15‑minute timeout (`STOP_REVIEW_TIMEOUT_MS`).

```javascript
// Conceptual flow as implemented in stop-review-gate-hook.mjs
const result = spawnSync(
  process.execPath,
  [companionScriptPath],
  {
    input: reviewPrompt,
    timeout: STOP_REVIEW_TIMEOUT_MS, // 15 minutes
    encoding: "utf8",
  }
);

```

If the process times out, exits with an error code, or returns malformed JSON, the gate treats this as a failure condition and prepares to block the stop.

## Parsing the Review Result

The helper function `parseStopReviewOutput` examines the first line of the companion's output:

- **`ALLOW:`** → The gate passes; the session may terminate normally.
- **`BLOCK:`** → The gate fails; the reason is captured for the user.
- **Any other response** → Treated as an unexpected answer, resulting in a block decision as a safety measure.

## Emitting Block Decisions

When blocking is required, the hook writes a JSON payload to STDOUT via `emitDecision`:

```json
{
  "decision": "block",
  "reason": "Codex stop-time review found issues: <specific reason here>"
}

```

The surrounding Codex runtime consumes this payload and **halts the stop operation**, keeping the session alive. Throughout this process, the hook continues writing helpful notes to STDERR using `logNote` so you understand why the stop was intercepted.

## What Happens When the Gate Blocks a Stop

When the OpenAI Codex plugin review gate blocks a stop, the following occurs:

1. **Session persistence**: The Claude session remains active instead of terminating.
2. **User notification**: You see the blocking reason (e.g., "Codex stop-time review found issues...") along with any notes about still-running jobs.
3. **Resolution paths**: You can address the reported problems directly, or bypass the gate using `/codex:review --wait` to complete pending reviews, or `/codex:cancel <jobId>` to terminate specific blocking jobs.

This mechanism enforces a final sanity check on your most recent code changes, automatically preventing session closure while outstanding issues remain unresolved.

## Code Example: Manual Gate Invocation

You can observe the gate behavior by invoking the hook directly as the Codex runtime does:

```javascript
import { spawnSync } from "node:child_process";
import path from "node:path";

const hookPath = path.resolve(
  __dirname,
  "plugins/codex/scripts/stop-review-gate-hook.mjs"
);

const input = {
  cwd: process.cwd(),
  last_assistant_message: "<Claude's previous turn output>",
  session_id: "session-123",
};

const result = spawnSync(
  process.execPath,
  [hookPath],
  {
    input: JSON.stringify(input),
    encoding: "utf8",
    timeout: 900000, // 15 minutes to match STOP_REVIEW_TIMEOUT_MS
  }
);

// Parse the JSON decision
const decision = JSON.parse(result.stdout);
console.log(decision.decision); // "allow" or "block"
console.log(decision.reason);    // Human-readable explanation if blocked

```

## Summary

- The **review gate** entry point is `stop-review-gate-hook.mjs`, which processes STDIN input and orchestrates the validation flow.
- The gate is controlled by the **`stopReviewGate`** flag in `state.mjs`, defaulting to **false**.
- Reviews use the **[`stop-review-gate.md`](https://github.com/openai/codex-plugin-cc/blob/main/stop-review-gate.md)** template injected with the previous Claude response via `interpolateTemplate`.
- The companion script **`codex-companion.mjs`** executes the review with a **15-minute timeout**.
- Results must start with **`ALLOW:`** or **`BLOCK:`**; anything else triggers a block.
- When blocked, the hook emits a JSON decision via `emitDecision`, causing the runtime to **halt the stop operation** and display the reasoning.

## Frequently Asked Questions

### How do I enable or disable the stop-review gate?

Set the `stopReviewGate` flag in the plugin state file managed by `state.mjs`. By default, this value is initialized to `false` (lines 19‑25), meaning you must explicitly enable it to activate the gate. When disabled, the hook logs running job status and exits without performing reviews.

### What happens if the review task times out?

If `codex-companion.mjs` exceeds the `STOP_REVIEW_TIMEOUT_MS` limit (15 minutes) or returns malformed JSON, the gate treats this as a failure condition. The `parseStopReviewOutput` logic interprets timeouts and errors as implicit **BLOCK** decisions, preventing the session from stopping until the review system responds correctly.

### Can I bypass the review gate when it blocks my session?

Yes. When the gate emits a block decision, you have two primary resolution paths: execute `/codex:review --wait` to allow pending reviews to complete and potentially resolve the blocking issues, or run `/codex:cancel <jobId>` to terminate specific running jobs that may be causing the blockage. Once resolved, you can issue the stop command again.

### Where does the gate get the content it reviews?

The gate extracts the previous Claude turn's output from the hook input's `last_assistant_message` field. This content is injected into the **[`stop-review-gate.md`](https://github.com/openai/codex-plugin-cc/blob/main/stop-review-gate.md)** template using `interpolateTemplate`, specifically replacing the `CLAUDE_RESPONSE_BLOCK` placeholder. This ensures Codex only evaluates the most recent changes rather than the entire session history.