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

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, 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.

  • 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/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, spawning it as a synchronous child process with a 15‑minute timeout (STOP_REVIEW_TIMEOUT_MS).

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

{
  "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:

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 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 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.

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 →