# How the Review Gate Feature and Stop Hook Operate in Codex

> Understand how Codex review gate and stop hook features work. Learn about the per-workspace flag that enables mandatory stop-time reviews, blocking sessions if needed.

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

---

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

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

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

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

```bash

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