Summary
When stopReviewGate: true, scripts/stop-review-gate-hook.mjs emits {"decision":"block","reason":"..."} on any of 5 fail-closed branches (timeout, task failure, invalid JSON, empty rawOutput, unexpected first-line). The reason string is the ONLY message operators see — Claude Code's Stop-hook does not offer a UI for hook decisions beyond the reason text.
The reason strings currently include "Run /codex:review --wait manually or bypass the gate." — but the concrete "bypass the gate" command is not spelled out. Operators in a fail-closed loop (e.g., 3 consecutive infra failures with empty rawOutput) cannot discover the escape valve /codex:setup --disable-review-gate from the message they see.
Failure scenario (observed)
A derivative Claude Code session on 2026-07-09 hit 3 consecutive status: 1 / rawOutput: "" failures from the underlying Codex CLI. Each time, the Stop hook fired at 15-min timeout limits and re-blocked. The session became terminaion-incapable — CC's built-in ~9-iteration recursive-stop limit was the only path out.
The operator recovered manually by finding the escape valve in tritool-side docs (.claude/rules/safety-gate.md §14). Without those tritool-side docs, the escape valve is not discoverable.
Proposed fix (3 options, any one closes the loop)
(a) Reason-string literal command (smallest change)
Append the literal command to all 5 fail-closed branches:
"Run /codex:review --wait manually or bypass the gate by running /codex:setup --disable-review-gate."
(b) Distinguish infra failure vs rule violation via reasonCategory field
CC's Stop-hook decision schema accepts extra fields. Adding:
{
"decision": "block",
"reason": "...",
"reasonCategory": "infra_failure" | "rule_violation"
}
lets downstream (e.g., tritool-side wrapper Stop hook) handle infra failures differently — e.g., downgrade to advisory after N consecutive infra failures.
(c) Consecutive-N counter + downgrade
Persist a counter in the plugin's state file (stateModule.setConfig). After N consecutive infra failures (recommended default N=3), downgrade decision: block → advisory ({"continue":true, "systemMessage":"..."}) so the session can terminate. Reset the counter on any allow / successful review / rule-violation block.
Reproduction
stopReviewGate: true (/codex:setup --enable-review-gate)
- Make the underlying Codex CLI fail 3 times consecutively (e.g., simulate by unavailable auth or config drift)
- Observe Stop hook blocks each iteration with the same reason string
- Confirm the operator cannot discover the escape valve from the reason string alone
Environment
- codex-plugin-cc: v1.0.4 (sha
807e03a at time of investigation)
- Claude Code: v2.1.198
- OS: Linux (verified 2026-07-09), likely applies to all platforms
Cross-reference
Tritool-template documented the discovery gap in docs/runbook-cc-codex-outage.md §8 (see linked repo URL) and .claude/rules/safety-gate.md §14 (Plugin hooks Stop row) as short-term workaround for their derivative projects. Filing here so the fix can land upstream and eliminate the need for the downstream doc.
Summary
When
stopReviewGate: true,scripts/stop-review-gate-hook.mjsemits{"decision":"block","reason":"..."}on any of 5 fail-closed branches (timeout, task failure, invalid JSON, empty rawOutput, unexpected first-line). Thereasonstring is the ONLY message operators see — Claude Code's Stop-hook does not offer a UI for hook decisions beyond the reason text.The reason strings currently include
"Run /codex:review --wait manually or bypass the gate."— but the concrete "bypass the gate" command is not spelled out. Operators in a fail-closed loop (e.g., 3 consecutive infra failures with empty rawOutput) cannot discover the escape valve/codex:setup --disable-review-gatefrom the message they see.Failure scenario (observed)
A derivative Claude Code session on 2026-07-09 hit 3 consecutive
status: 1 / rawOutput: ""failures from the underlying Codex CLI. Each time, the Stop hook fired at 15-min timeout limits and re-blocked. The session became terminaion-incapable — CC's built-in ~9-iteration recursive-stop limit was the only path out.The operator recovered manually by finding the escape valve in tritool-side docs (
.claude/rules/safety-gate.md§14). Without those tritool-side docs, the escape valve is not discoverable.Proposed fix (3 options, any one closes the loop)
(a) Reason-string literal command (smallest change)
Append the literal command to all 5 fail-closed branches:
(b) Distinguish infra failure vs rule violation via
reasonCategoryfieldCC's Stop-hook decision schema accepts extra fields. Adding:
{ "decision": "block", "reason": "...", "reasonCategory": "infra_failure" | "rule_violation" }lets downstream (e.g., tritool-side wrapper Stop hook) handle infra failures differently — e.g., downgrade to advisory after N consecutive infra failures.
(c) Consecutive-N counter + downgrade
Persist a counter in the plugin's state file (
stateModule.setConfig). After N consecutive infra failures (recommended default N=3), downgradedecision: block→ advisory ({"continue":true, "systemMessage":"..."}) so the session can terminate. Reset the counter on any allow / successful review / rule-violation block.Reproduction
stopReviewGate: true(/codex:setup --enable-review-gate)Environment
807e03aat time of investigation)Cross-reference
Tritool-template documented the discovery gap in
docs/runbook-cc-codex-outage.md§8 (see linked repo URL) and.claude/rules/safety-gate.md§14 (Plugin hooks Stop row) as short-term workaround for their derivative projects. Filing here so the fix can land upstream and eliminate the need for the downstream doc.