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 instate.jsonthat 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.mdprompt template andcodex-companion.mjs. - The hook expects the review to output
ALLOW:orBLOCK:on the first line; it emits{"decision": "block"}to STDOUT to halt the session if the review fails. - Configuration is managed through
setConfigandgetConfiginstate.mjs, with state stored inresolveStateDir(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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →