How the Review Gate Hook Blocks Claude Responses Using Codex Feedback in openai/codex-plugin-cc
The review gate hook implements a hard blocking mechanism by spawning a Codex review process that analyzes the previous Claude response and emits a JSON block decision to STDOUT when issues are detected.
The review gate hook is a critical safety component in the openai/codex-plugin-cc repository that intercepts Claude's turn completion to enforce quality standards. According to the source code, this hook runs as a Claude plugin script that determines whether the current assistant turn may finish or must be aborted based on automated feedback from OpenAI's Codex model.
Hook Architecture and Entry Point
The review gate logic resides in plugins/codex/scripts/stop-review-gate-hook.mjs, which serves as the Claude plugin hook entry point.
When invoked, the main() function reads the JSON payload from STDIN using readHookInput to obtain the working directory and previous assistant message. It then loads per-workspace configuration via getConfig and determines the session context needed for isolation.
// From plugins/codex/scripts/stop-review-gate-hook.mjs
const input = await readHookInput(); // Reads from STDIN
const cwd = input.cwd;
const config = await getConfig(cwd);
Session-Aware Job Filtering
To prevent unrelated tasks from influencing the decision, the hook implements strict session isolation. The filterJobsForCurrentSession function extracts the session ID from either the hook input or the CLAUDE_SESSION_ID environment variable (SESSION_ID_ENV), ensuring only jobs belonging to the current Claude session are considered during the review process.
This isolation prevents cross-contamination between concurrent Claude conversations when multiple workspaces or sessions are active simultaneously.
Early Exit Conditions
The hook implements several guard clauses to avoid unnecessary blocking when the gate is disabled or Codex is unavailable:
config.stopReviewGateis false: The hook logs any running task notes and returns immediately, allowing the turn to complete without review.- Codex is not configured: The
buildSetupNotefunction callsgetCodexAvailability(defined incodex.mjs) to verify the Codex binary and app-server are present. If setup is incomplete, the hook logs a warning and exits without blocking. - A Codex task is still running: If
runningJobexists, the hook logs the active task status but allows the turn to proceed unless the subsequent review mandates a block.
Stop-Gate Review Execution
When the gate is enabled and Codex is ready, the hook constructs a specialized prompt for the stop-time review. The buildStopReviewPrompt function loads the markdown template stop-review-gate.md via loadPromptTemplate and interpolates the previous Claude output into the {{CLAUDE_RESPONSE_BLOCK}} placeholder.
The review is executed by spawning a child process that runs codex-companion.mjs with the task --json command:
node plugins/codex/scripts/codex-companion.mjs task --json \
--prompt "prompt-content" \
--cwd /workspace/path
The child process inherits the current environment, optionally setting CLAUDE_SESSION_ID for session-specific isolation, ensuring the review runs in the correct context.
Parsing Codex Output and Blocking Logic
The parseStopReviewOutput function examines the rawOutput field from the Codex JSON response, specifically checking the first line for control directives:
"ALLOW:": Returns{ ok: true, reason: null }, permitting the turn to complete."BLOCK:": Returns{ ok: false, reason: <extracted text> }, triggering the block mechanism.- Any other output or empty response: Treated as a failure with a generic reason.
If runStopReview returns a non-OK result, the hook immediately writes a JSON decision to STDOUT using emitDecision:
{"decision":"block","reason":"Codex stop-time review found issues in the generated code"}
The Claude runtime interprets this JSON as a hard block, aborting the turn and displaying the supplied reason to the user. If the review succeeds (ok: true), the hook exits silently without emitting a decision, allowing normal turn completion.
Error Handling and Safety Guarantees
All top-level execution is wrapped in a try/catch block to ensure the hook never silently succeeds on failure. Unexpected errors are written to STDERR with a non-zero exit code, forcing Claude to recognize the hook failure and prevent potential bypass of the review gate.
This design ensures that infrastructure failures or Codex process timeouts default to a safe state rather than allowing unreviewed code to pass through.
Summary
- The review gate hook (
stop-review-gate-hook.mjs) implements a blocking mechanism that intercepts Claude turn completion based on Codex feedback. - Session isolation via
CLAUDE_SESSION_IDprevents unrelated jobs from affecting the decision. - Early exit conditions check for disabled configuration, missing Codex setup, or running tasks before executing the review.
- The stop-review prompt uses the
stop-review-gate.mdtemplate to evaluate the previous Claude response. - Binary decision parsing looks for
ALLOW:orBLOCK:prefixes in Codex output to determine the final result. - JSON emission to STDOUT with
{"decision":"block"}triggers Claude's hard block mechanism. - Comprehensive error handling ensures failures result in process termination rather than silent bypass.
Frequently Asked Questions
How does the review gate hook communicate its decision to Claude?
The hook communicates by writing a JSON object to STDOUT using the emitDecision function. When Codex detects blocking issues, the hook outputs {"decision":"block","reason":"explanation"}. The Claude runtime captures this output and aborts the current turn, displaying the reason to the user. If no block is necessary, the hook exits without writing to STDOUT, signaling permission to proceed.
What happens if Codex is not installed or configured when the hook runs?
The hook calls buildSetupNote, which invokes getCodexAvailability to verify the Codex binary and app-server presence. If Codex is unavailable, the hook logs a setup warning and returns without blocking. This prevents hard failures during initial setup while alerting users to configuration issues.
Can the review gate be disabled for specific workspaces?
Yes. The hook checks config.stopReviewGate loaded via getConfig(cwd). If this boolean is set to false, the hook immediately returns after logging any running task notes, effectively bypassing the review process for that workspace while preserving the hook infrastructure.
What timeout or error handling exists if the Codex review process fails?
If the child process spawning codex-companion.mjs times out, exits with a non-zero status, or returns unparsable output, the parseStopReviewOutput function treats these as failures with generic error reasons. The top-level try/catch block ensures all unexpected errors write to STDERR and exit with a non-zero code, preventing the hook from silently failing open and allowing unreviewed content through.
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 →