How the Review Gate Feature and Stop Hook Operate in Codex

The review gate is a per-workspace boolean flag that, when enabled, forces a mandatory stop-time review via the stop-hook script before a Codex session can terminate, blocking the session if the review returns BLOCK: instead of ALLOW:.

The review gate feature in the openai/codex-plugin-cc repository provides a configurable safety mechanism that intercepts session termination to perform final code quality checks. When activated, the stop hook (plugins/codex/scripts/stop-review-gate-hook.mjs) executes a Codex-driven review task that evaluates the session state and either permits the stop or prevents it based on the review outcome. This system relies on persistent workspace state, structured JSON communication, and a specific prompt template to enforce safety gates at session boundaries.

Understanding the Review Gate Configuration

The review gate is controlled by the stopReviewGate boolean flag stored in the plugin’s persistent state. This flag resides in the workspace-specific state.json file within the state directory returned by resolveStateDir, which defaults to a temporary folder or the path specified by the $CLAUDE_PLUGIN_DATA/state environment variable.

The state schema includes a configuration object and job tracking:

{
  "config": {
    "stopReviewGate": false
  },
  "jobs": [ … ]
}

You can toggle this flag programmatically using the setConfig function from plugins/codex/scripts/lib/state.mjs, which updates the JSON file atomically. Alternatively, users can enable or disable the gate using the /codex:setup command, which flips config.stopReviewGate between true and false.

How the Stop Hook Executes the Review

When a session ends, the stop hook receives control and executes a seven-step logic flow defined in stop-review-gate-hook.mjs. This process determines whether the session can terminate immediately or must undergo a review.

Receiving Hook Input via STDIN

The hook receives a JSON payload on standard input containing the current working directory (cwd), the last assistant message, and optionally the session ID. This input allows the hook to locate the correct workspace context for evaluation.

Loading Workspace-Specific State

Using the cwd from the input, the hook calls getConfig to load the workspace configuration. This function reads the state.json file from the resolved state directory, ensuring the hook respects the per-workspace review gate setting.

Fast-Path When the Gate Is Disabled

If config.stopReviewGate is false, the hook bypasses the review entirely. It simply queries for any still-running Codex jobs using listJobs and logs them to STDERR, then returns successfully to allow immediate session termination.

Triggering the Stop-Time Review Task

When the gate is enabled, the hook performs several checks before running the review. First, it verifies that Codex is properly configured for the review gate via buildSetupNote. If the Codex service is unavailable, it logs a helpful diagnostic and exits without blocking the stop.

If the service is available, the hook constructs a review prompt using the template at plugins/codex/prompts/stop-review-gate.md. It then spawns the companion script (codex-companion.mjs) with the task subcommand, passing the constructed prompt. This child process runs with a hard 15-minute timeout defined by STOP_REVIEW_TIMEOUT_MS.

Parsing the Review Decision

The hook interprets the task output through parseStopReviewOutput, which expects the first line of the Codex response to be either ALLOW: (indicating review passed) or BLOCK: (indicating review failed). Any other output format is treated as an error condition.

Emitting the Final Decision

If the review task returns ok: false or the parsed output indicates a block, the hook writes a JSON object to STDOUT: {"decision": "block", "reason": "..."}. This JSON signal causes the Codex session to halt. If the review succeeds, the hook prints any still-running job notes to STDERR and exits cleanly, allowing the session to finish.

State Management and Job Tracking

All job-related utilities in plugins/codex/scripts/lib/state.mjs—including listJobs, upsertJob, and resolveJobFile—read from the same state directory. This ensures that when the stop hook checks for running jobs or configuration values, it sees the current, consistent state of the workspace. The atomic update mechanism in setConfig prevents race conditions when toggling the review gate during active sessions.

Key Implementation Files

The review gate system spans several files in the openai/codex-plugin-cc repository:

File Purpose
plugins/codex/scripts/stop-review-gate-hook.mjs Core implementation of the stop-time review logic and decision emission.
plugins/codex/scripts/lib/state.mjs Persists the stopReviewGate flag and provides getConfig/setConfig functions.
plugins/codex/prompts/stop-review-gate.md Template used to prompt Codex for the stop-time safety review.
plugins/codex/scripts/codex-companion.mjs Executes Codex tasks including the stop-review task with timeout handling.
plugins/codex/scripts/lib/job-control.mjs Marks jobs requiring review when the gate is enabled.
plugins/codex/scripts/lib/render.mjs Renders state reports showing the current review gate status.

Practical Implementation Examples

Enabling the Review Gate Programmatically

To enable the review gate for the current workspace via JavaScript:

import { resolveWorkspaceRoot } from "./lib/workspace.mjs";
import { setConfig } from "./lib/state.mjs";

const cwd = process.cwd();
const root = resolveWorkspaceRoot(cwd);
await setConfig(root, "stopReviewGate", true);
console.log("Stop-time review gate enabled.");

Simplified Stop Hook Logic

The following illustrates the core decision logic within the stop hook:

import { getConfig, listJobs } from "./lib/state.mjs";
import { runStopReview } from "./stop-review-gate-hook.mjs";

function stopHook(input) {
  const cwd = input.cwd ?? process.cwd();
  const config = getConfig(cwd);

  // If gate disabled, just report running jobs
  if (!config.stopReviewGate) {
    const running = listJobs(cwd).filter(j => j.status === "running");
    if (running.length) console.error(`Codex task ${running[0].id} still running`);
    return;
  }

  // Gate enabled → perform stop-time review
  const review = runStopReview(cwd, input);
  if (!review.ok) {
    // block the stop
    console.log(JSON.stringify({ decision: "block", reason: review.reason }));
  }
}

Command-Line Configuration

Users can enable the gate via the CLI before working:


# Enable the gate

codex:setup --review-gate

# Work on your project...

# When you request a stop, Codex automatically runs the stop-review-gate hook.

# If the review passes, the session ends; otherwise you see a BLOCK message.

Summary

  • The review gate is a workspace-specific boolean flag (stopReviewGate) stored in state.json that defaults to disabled.
  • The stop hook (stop-review-gate-hook.mjs) intercepts session termination and checks this flag before allowing the stop to proceed.
  • When enabled, the hook spawns a 15-minute timed review task using the stop-review-gate.md prompt template and codex-companion.mjs.
  • The hook expects the review to output ALLOW: or BLOCK: on the first line; it emits {"decision": "block"} to STDOUT to halt the session if the review fails.
  • Configuration is managed through setConfig and getConfig in state.mjs, with state stored in resolveStateDir (respecting $CLAUDE_PLUGIN_DATA/state).

Frequently Asked Questions

What happens if the review gate is disabled?

When stopReviewGate is set to false, the stop hook immediately returns after logging any still-running Codex jobs to STDERR. The session terminates without performing the AI review, allowing for faster shutdown when safety checks are not required.

How long does the stop-time review wait before timing out?

The hook enforces a 15-minute timeout (STOP_REVIEW_TIMEOUT_MS) on the review task spawned via codex-companion.mjs. If the review does not complete within this window, the task is terminated and treated as a failure condition.

What specific output must Codex provide to allow a session to end?

The parseStopReviewOutput function expects the first line of the Codex response to be exactly ALLOW:. If the first line is BLOCK:, the hook interprets this as a failed review and emits a JSON block decision. Any other output format is treated as an error and will also block the session.

Where is the review gate configuration stored?

The configuration is persisted in a state.json file located in the state directory returned by resolveStateDir. This directory typically resides under a temporary folder, or under $CLAUDE_PLUGIN_DATA/state when that environment variable is set. The setConfig function in state.mjs handles atomic updates to this file.

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 →