How the Stop-Review-Gate Functionality Works in openai/codex-plugin-cc: A Complete Technical Guide
The stop-review-gate functionality in openai/codex-plugin-cc is an automated safety layer that performs a Codex-driven review of the previous Claude turn before allowing a session to terminate, emitting either an "ALLOW" or "BLOCK" decision via JSON to STDOUT.
The openai/codex-plugin-cc repository implements the stop-review-gate functionality as a final checkpoint to prevent problematic code changes from persisting when a Claude session ends. This gate intercepts stop commands, validates the recent work through a Codex companion process, and either permits the session to close or blocks it until issues are resolved.
Entry Point and Hook Architecture
The stop-review-gate functionality begins execution at stop-review-gate-hook.mjs located in plugins/codex/scripts/. This Node.js module serves as the orchestration layer that coordinates the entire review process.
The hook receives its input via STDIN through the readHookInput function, which parses a JSON payload containing the current working directory, session ID, and the previous assistant message. After resolving the workspace root, the script loads the plugin configuration using getConfig and gathers metadata about any active Codex jobs by calling listJobs followed by filterJobsForCurrentSession.
Configuration and State Management
Whether the gate executes depends on the stopReviewGate boolean flag stored in the state file at plugins/codex/scripts/lib/state.mjs. According to the source code, the default initialization sets stopReviewGate: false (lines 19‑25), meaning the gate remains inactive unless explicitly enabled.
When the flag is disabled, the hook immediately logs any running job notes and exits without performing a review. If enabled, the hook proceeds to buildSetupNote to verify that Codex is properly configured for the workspace. When setup is incomplete, the script logs a diagnostic message and aborts to prevent invalid review attempts.
Review Prompt Construction
Once validated, the gate constructs the review prompt using the template file stop-review-gate.md in plugins/codex/prompts/. The interpolateTemplate function (from lib/prompts.mjs) injects the previous Claude response into the CLAUDE_RESPONSE_BLOCK placeholder within the template.
The resulting prompt instructs Codex to review only the work from the previous Claude turn and return a compact answer beginning with either ALLOW: or BLOCK:. This strict output format enables deterministic parsing of the review results.
Executing the Review Task
The hook launches the actual review by spawning codex-companion.mjs (plugins/codex/scripts/codex-companion.mjs) as a synchronous child process using spawnSync. The task executes with a hard timeout of 15 minutes defined by STOP_REVIEW_TIMEOUT_MS.
If the companion process times out, exits with an error code, or returns malformed JSON, the gate treats these conditions as failures and prepares a blocking decision. This ensures that infrastructure failures default to a safe state rather than allowing potentially flawed code to pass unchecked.
Decision Parsing and Emission
After the companion task completes, the parseStopReviewOutput helper examines the first line of the output:
ALLOW:→ The gate passes and the session may terminate.BLOCK:→ The gate fails and the session must remain active.- Any other output → Treated as an unexpected answer, resulting in a block.
When blocking is required, the hook calls emitDecision to write a JSON object to STDOUT with the structure { decision: "block", reason: "..." }. The surrounding Codex runtime consumes this payload and halts the stop operation, keeping the session alive. A note about any still-running jobs is appended to the reason for completeness.
Throughout execution, the hook writes diagnostic information to STDERR via logNote, ensuring users understand why a stop was blocked or why no review occurred.
What Happens When the Gate Blocks a Stop
When the stop-review-gate functionality blocks a session termination, the user receives the blocking reason (for example, "Codex stop-time review found issues...") and the session remains open. The user must then either address the reported problems or bypass the gate using specific commands such as /codex:review --wait or /codex:cancel <jobId>.
This design enforces a final sanity check on the most recent code changes, ensuring that outstanding issues cannot be accidentally committed by closing the session.
Code Examples
The following example demonstrates how the Codex runtime manually invokes the stop-review gate:
import { spawnSync } from "node:child_process";
import path from "node:path";
const hookPath = path.resolve(
__dirname,
"plugins/codex/scripts/stop-review-gate-hook.mjs"
);
const input = {
cwd: process.cwd(),
last_assistant_message: "<Claude's previous turn output>",
session_id: "session-123",
};
const result = spawnSync(
process.execPath,
[hookPath],
{
input: JSON.stringify(input),
encoding: "utf8",
}
);
console.log(result.stdout); // JSON decision: { "decision": "allow" } or { "decision": "block", "reason": "..." }
When using the interactive CLI, the gate triggers automatically:
# Pseudo-command that internally triggers the gate
/codex:stop
# Internal execution flow:
# 1️⃣ stop-review-gate-hook.mjs reads hook input via readHookInput
# 2️⃣ Builds prompt from stop-review-gate.md using interpolateTemplate
# 3️⃣ Runs codex-companion.mjs task with 15-minute timeout
# 4️⃣ Parses output with parseStopReviewOutput
# 5️⃣ Emits block decision if first line is "BLOCK:"
Summary
- The entry point for the stop-review-gate functionality is
stop-review-gate-hook.mjs, which orchestrates the review process. - Execution is controlled by the
stopReviewGateflag inplugins/codex/scripts/lib/state.mjs, defaulting tofalse. - The review uses the
stop-review-gate.mdprompt template to instruct Codex to examine only the previous Claude turn. - The companion script
codex-companion.mjsexecutes the review synchronously with a 15-minute timeout (STOP_REVIEW_TIMEOUT_MS). - Valid outputs must begin with
ALLOW:orBLOCK:; any other response triggers a block. - The hook emits a JSON decision via
emitDecisionto STDOUT, which the runtime uses to either permit or halt session termination.
Frequently Asked Questions
How do I enable the stop-review-gate functionality?
You must set the stopReviewGate flag to true in the plugin state. By default, plugins/codex/scripts/lib/state.mjs initializes this value to false (lines 19‑25), so you need to explicitly toggle it to activate the safety check.
Which file contains the review prompt template?
The prompt template resides at plugins/codex/prompts/stop-review-gate.md. This Markdown file defines the instructions that Codex follows when reviewing the previous turn's changes, including the requirement to prefix responses with ALLOW: or BLOCK:.
What happens if the review task times out or crashes?
If the codex-companion.mjs process exceeds the 15-minute timeout (STOP_REVIEW_TIMEOUT_MS), exits with an error, or returns malformed JSON, the gate treats these as failures. The parseStopReviewOutput function will interpret these conditions as a blocking event, preventing the session from stopping until manually resolved.
Can I bypass the gate if it incorrectly blocks my session?
Yes. When the gate blocks a stop, you can bypass it by using the /codex:review --wait command to wait for pending reviews to complete, or /codex:cancel <jobId> to cancel specific running jobs. The hook logs the specific blocking reason to STDERR via logNote, allowing you to diagnose whether to fix the reported issues or override the gate.
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 →