openai / openai/codex-plugin-cc
Stop review gate blocks Claude on infrastructure errors, causing rewake loops
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 33.3k
- Forks
- 2.3k
- PR merge metrics
- No merged PRs in 30d
Description
Summary
scripts/stop-review-gate-hook.mjs conflates two distinct outcomes into the same { decision: "block" } hook
output:
- Genuine BLOCK — Codex task returned
BLOCK: <reason>(a real policy decision) - Infrastructure failure — the Codex task could not produce a valid ALLOW/BLOCK decision at all (timeout,
non-zero exit, empty output, invalid JSON)
Combined with Claude Code's rewake-on-block semantic, any transient upstream failure (ChatGPT usage limit, auth
expiry, network blip, Codex app-server crash) creates a sustained rewake loop. Claude replies → hook fires → Codex
still failing → block → Claude rewakes → … The loop burns Claude tokens continuously and produces zero useful review,
and the user has no way out other than /codex:setup --disable-review-gate.
Reproduction
- Enable the gate:
/codex:setup --enable-review-gate - Induce any transient Codex-side failure. Easiest repro: exhaust your ChatGPT / Codex usage quota.
- Finish any Claude turn (even a trivial one).
- The hook's spawned
codex taskexits withstatus: 1and empty output. The hook emits{ decision: "block" }. - Claude Code rewakes the model; Claude replies; the hook fires again with the same failure; loop continues until
the user manually disables the gate or the quota resets.
In a real session I observed ~10 consecutive failed jobs in
~/.claude/plugins/data/codex-openai-codex/state/<workspace>-<hash>/state.json, all with:
"summary": "You've hit your usage limit. To get more access now, send a request to your admin or try again at 7:32
PM."
"status": "failed"
Each one produced a BLOCK decision to Claude Code, despite no review having actually run.
Root cause
In scripts/stop-review-gate-hook.mjs:
-
parseStopReviewOutput()returns{ ok: false, reason }for bothBLOCK: ...responses and any
unexpected/empty output. -
runStopReview()returns{ ok: false, reason }for timeouts, non-zero exits, and JSON parse failures. -
main()treats every non-okresult identically:if (!review.ok) { emitDecision({ decision: "block", reason: ... }); return; }
There is no code path for "the review didn't run — don't block, just warn". All infrastructure failures are mapped to
the same blocking decision as a real Codex BLOCK.
Proposed fix
Distinguish outcomes with a tagged return type:
{ kind: "allow" }— Codex said ALLOW{ kind: "block", reason }— Codex said BLOCK{ kind: "infra_error", reason }— timeout / non-zero exit / empty / invalid JSON
Then in main():
block→ existing behavior (emitDecision({ decision: "block", reason }))infra_error→logNote()with actionable reason, exit without blocking Claudeallow→ existing behavior
This preserves all correct behavior (real BLOCKs still block; ALLOWs still allow) and only changes the path where the
review cannot run. A user whose Codex is misconfigured or quota-limited now sees a single warning note per turn
instead of an infinite loop.
Happy to submit a PR with this fix plus a regression test (mock spawnSync returning status: 1, assert
emitDecision is not called). Flagging as issue first to confirm the direction before submitting code.
Alternatives considered
- Circuit breaker (auto-disable gate after N consecutive blocks): mitigates the symptom but doesn't fix the
conflation. A real sequence of legitimate BLOCKs (user genuinely refusing to fix an issue) would also trigger it. - Pattern-match known error strings in
main(): fragile, can't keep up with new failure modes. - Reuse
buildSetupNote()for runtime failures: that path only covers "Codex not installed / not logged in"
before any task runs, not failures during task execution. - Retry on infra error: multiplies token cost when backend is genuinely unavailable.
Environment
- Plugin version:
codex@openai-codexv1.0.4 - Platform: macOS (Darwin 25.4.0)
- Node: v22.19.0
- Auth: ChatGPT (consumer)
Related
No existing open or closed issue covers this specific conflation (searched open + closed for review gate, stop hook, BLOCK, loop, quota, rawOutput, status 1). Adjacent issues: #191 (Windows-specific stdin blocking,
opposite scenario), #213 (user-level default for stopReviewGate, feature request), #152 (/codex:usage command,
quota visibility). None addresses the decision-mapping path in the hook itself.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with scripts/stop-review-gate-hook.mjs, reading parseStopReviewOutput(), runStopReview(), and main() to trace how BLOCK results differ from timeouts and failed commands. Run the hook tests, then add the proposed spawnSync regression case and verify that genuine BLOCK and ALLOW decisions remain unchanged while infrastructure failures do not call emitDecision.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100