Review Gate Feature in the Codex Plugin: How It Blocks Claude Stop Commands
The review gate feature intercepts Claude's stop command by running an automated safety review that emits a block decision, preventing session termination until the review explicitly returns ALLOW or the gate is disabled.
The review gate feature in the openai/codex-plugin-cc repository adds a mandatory approval step before Claude can end a conversation. When enabled via the stopReviewGate configuration flag, the plugin injects a hook into the stop command pipeline that evaluates the session's final state using automated Codex analysis before allowing termination.
Configuring the Review Gate
The review gate is controlled by the stopReviewGate boolean stored in state.json under config.stopReviewGate (default: false). You toggle this setting using the /codex:setup command with explicit flags.
To enable the gate:
!codex setup --enable-review-gate
To disable the gate:
!codex setup --disable-review-gate
Internally, these commands invoke setConfig(workspaceRoot, "stopReviewGate", true) or false in plugins/codex/scripts/codex-companion.mjs (lines 29‑34). This writes the value to persistent storage via plugins/codex/scripts/lib/state.mjs, ensuring the setting survives across sessions.
Intercepting Stop Commands with the Hook
When a user issues a stop request, the stop-review-gate-hook (plugins/codex/scripts/stop-review-gate-hook.mjs) executes before Claude terminates the session. The hook reads the incoming JSON payload, resolves the workspaceRoot, and retrieves the current configuration.
If config.stopReviewGate is disabled, the hook logs and exits without interference (lines 54‑57). When enabled, the hook builds a review prompt using the template at plugins/codex/prompts/stop-review-gate.md and spawns a synchronous review task:
const result = spawnSync(process.execPath,
[scriptPath, "task", "--json", prompt], { … });
This execution at lines 99‑106 of the hook script triggers the stop-time Codex review, analyzing the conversation context to determine if termination is safe.
Parsing the Review Decision
The hook parses the review task's stdout using parseStopReviewOutput (lines 80‑95). This function examines the first line of output to determine the verdict:
ALLOW:→ Review passes, permitting the stop command to proceedBLOCK:→ Review fails; the function extracts the reason string after the colon- Other → Treated as an unexpected failure, defaulting to a block state
The parsing logic strictly differentiates between explicit permission and denial based on the prefix format, ensuring no ambiguous states pass through to the runtime.
Blocking Mechanism Implementation
When the review returns a non-allow verdict, the hook constructs a block decision JSON object:
emitDecision({
decision: "block",
reason: review.reason
});
This executes at lines 66‑71 of stop-review-gate-hook.mjs. The emitDecision function outputs a structured JSON response that the Codex Plugin runtime consumes. When the runtime detects decision: "block", it prevents Claude from terminating the session and surfaces the explanatory reason to the user.
The block persists until the user resolves the flagged issue or disables the review gate via configuration. Additionally, plugins/codex/scripts/lib/job-control.mjs marks jobs requiring review when the gate is active, providing visibility into which sessions are under enforcement.
Summary
- The review gate feature adds a mandatory safety review before Claude can execute a stop command.
- Configuration persists in
state.jsonviasetConfigincodex-companion.mjs, toggled through--enable-review-gateor--disable-review-gateflags. - The stop-review-gate-hook intercepts every stop request and runs a Codex review task when enabled at the workspace level.
- Reviews parse output for
ALLOW:orBLOCK:prefixes inparseStopReviewOutput; any non-allow result triggersemitDecisionwithdecision: "block". - The block prevents session termination until explicitly cleared or the gate is disabled by the user.
Frequently Asked Questions
How do I know if the review gate is blocking my stop command?
When blocked, the plugin emits a JSON decision object containing decision: "block" and a specific reason string extracted from the review output. This surfaces in the interface as a termination prevention message with details about why the review flagged the session, such as unfinished tasks or safety concerns.
Can the review gate be enabled per workspace or only globally?
The configuration is scoped to a workspaceRoot via setConfig(workspaceRoot, "stopReviewGate", value), allowing individual workspaces to maintain separate gate states. Each workspace's state.json stores its own boolean flag independently, so you can enforce the gate on specific projects while leaving others unrestricted.
What happens if the review task crashes or returns unexpected output?
The parseStopReviewOutput function in stop-review-gate-hook.mjs treats any output not starting with ALLOW: or BLOCK: as an unexpected failure, which defaults to a blocking state. This fail-safe design ensures that malformed reviews, process errors, or crashes prevent potentially unsafe session terminations rather than allowing them to slip through unchecked.
Where can I customize the prompt used for stop-time reviews?
The review prompt template lives in plugins/codex/prompts/stop-review-gate.md. Modifying this file changes the instructions and context provided to Codex during the stop-time review, although the parsing logic in the hook still expects ALLOW: or BLOCK: prefixes in the response to determine the final decision.
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 →