Problem
OpenCode review produced no model verdict at all between 2026-08-27 and 2026-09-21, and the opencode-review check stayed green for much of that window. The outage was invisible in check status because the model-unavailable evidence fallback concludes success while reporting model=none.
This is an observability defect, not an authorization one. See "Not a merge-authorization hole" below.
Reproduction evidence
Run 34931908846, job opencode-review, head 91be6442 — the run that was cited as the last successful OpenCode review:
CONTEXTUAL_ORCHESTRATOR_BASE_URL: http://127.0.0.1:18080
Configured OpenCode model pool: candidates=1 attempts=1 max-cycles=1
OpenCode contextual-orchestrator/orchestrator/free attempt 1/1 failed with exit 1.
OpenCode provider failure metadata: class=provider-error json-bytes=836 stderr-bytes=0; provider-controlled content suppressed.
OpenCode completed a full model-candidate cycle without a valid control conclusion.
OpenCode model pool exhausted before producing a valid control conclusion.
OPENCODE_MODEL_POOL_OUTCOME: exhausted
OpenCode model-pool outcome=exhausted model=none; publish stage performs no duplicate model-catalog pass.
##[notice]Current-head model-unavailable evidence fallback candidate: scope=central OpenCode/Strix review-process changed_count=1 ...
The job concluded success. Every run in that window also reported provider secrets present: 5 of 5 and a gateway whose own preflight returned status: ready, so nothing in the check surface suggested an outage.
The underlying request failure is fixed in #2333 (the OpenCode provider baseURL omitted the gateway's served /v1 prefix, so the gateway answered route_not_found whose message is the bare string not found). That fix does not address this: the next provider-side outage will go green the same way.
Where it happens
.github/workflows/opencode-review-dispatch.yml:6954 — publish_blockers_after_model_unavailable() runs whenever COVERAGE_EVIDENCE_RESULT is success, publishes the fallback notice, and lets the job conclude success.
- The only signals that a model never ran are an
::notice:: line and OPENCODE_MODEL_POOL_OUTCOME: exhausted inside the log. Neither surfaces in check status, and neither is counted anywhere across runs.
Not a merge-authorization hole
Worth stating explicitly so the fix does not get aimed at the wrong gate. The fallback never grants approval:
scripts/ci/opencode_existing_approval_gate.py:22 lists "model-unavailable evidence fallback" in FALLBACK_MARKERS and rejects such a body at line 137.
scripts/ci/opencode_review_receipt_gate.py:41 does the same via FALLBACK_APPROVAL_MARKERS (line 134).
merge_approval_block_reason in scripts/ci/pr_review_merge_scheduler_core.py independently requires reviewDecision == APPROVED plus a non-author formal approval on the exact current head.
So merges stayed correctly blocked. What broke is detection: a three-week model outage looked like a healthy pipeline.
Suggested direction
Make the fallback path observable rather than tightening approval gates:
- Emit a distinct, greppable terminal marker when a check concludes through the fallback with
model=none, separate from ordinary success.
- Track consecutive fallback conclusions per repository and raise an alert past a small threshold — one fallback is a transient provider blip, twenty in a row is an outage.
- Consider surfacing the fallback state in the check's output summary so it is visible without opening the run log.
Related but distinct: #2112 (suppressed provider failure envelope discards the cause — which is why the not found body was never visible here) and #626 (fallback allowlist drift).
🤖 Generated with Claude Code
https://claude.ai/code/session_01JMBfufaYsYU8hviiA75Xb3
Problem
OpenCode review produced no model verdict at all between 2026-08-27 and 2026-09-21, and the
opencode-reviewcheck stayed green for much of that window. The outage was invisible in check status because the model-unavailable evidence fallback concludessuccesswhile reportingmodel=none.This is an observability defect, not an authorization one. See "Not a merge-authorization hole" below.
Reproduction evidence
Run
34931908846, jobopencode-review, head91be6442— the run that was cited as the last successful OpenCode review:The job concluded
success. Every run in that window also reportedprovider secrets present: 5 of 5and a gateway whose own preflight returnedstatus: ready, so nothing in the check surface suggested an outage.The underlying request failure is fixed in #2333 (the OpenCode provider
baseURLomitted the gateway's served/v1prefix, so the gateway answeredroute_not_foundwhose message is the bare stringnot found). That fix does not address this: the next provider-side outage will go green the same way.Where it happens
.github/workflows/opencode-review-dispatch.yml:6954—publish_blockers_after_model_unavailable()runs wheneverCOVERAGE_EVIDENCE_RESULTissuccess, publishes the fallback notice, and lets the job concludesuccess.::notice::line andOPENCODE_MODEL_POOL_OUTCOME: exhaustedinside the log. Neither surfaces in check status, and neither is counted anywhere across runs.Not a merge-authorization hole
Worth stating explicitly so the fix does not get aimed at the wrong gate. The fallback never grants approval:
scripts/ci/opencode_existing_approval_gate.py:22lists"model-unavailable evidence fallback"inFALLBACK_MARKERSand rejects such a body at line 137.scripts/ci/opencode_review_receipt_gate.py:41does the same viaFALLBACK_APPROVAL_MARKERS(line 134).merge_approval_block_reasoninscripts/ci/pr_review_merge_scheduler_core.pyindependently requiresreviewDecision == APPROVEDplus a non-author formal approval on the exact current head.So merges stayed correctly blocked. What broke is detection: a three-week model outage looked like a healthy pipeline.
Suggested direction
Make the fallback path observable rather than tightening approval gates:
model=none, separate from ordinary success.Related but distinct: #2112 (suppressed provider failure envelope discards the cause — which is why the
not foundbody was never visible here) and #626 (fallback allowlist drift).🤖 Generated with Claude Code
https://claude.ai/code/session_01JMBfufaYsYU8hviiA75Xb3