# How the Review Gate Hook Blocks Claude Responses Using Codex Feedback in openai/codex-plugin-cc

> Discover how the review gate hook in openai/codex-plugin-cc blocks Claude responses. Learn how Codex feedback triggers a blocking mechanism via JSON decision to STDOUT.

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

---

**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.

```javascript
// 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.stopReviewGate` is false**: The hook logs any running task notes and returns immediately, allowing the turn to complete without review.
- **Codex is not configured**: The `buildSetupNote` function calls `getCodexAvailability` (defined in `codex.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 `runningJob` exists, 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`](https://github.com/openai/codex-plugin-cc/blob/main/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:

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

```json
{"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_ID` prevents 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.md`](https://github.com/openai/codex-plugin-cc/blob/main/stop-review-gate.md) template to evaluate the previous Claude response.
- **Binary decision parsing** looks for `ALLOW:` or `BLOCK:` 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.