Repository navigation
fix(opencode): model-unavailable fallback hides a multi-week review outage behind a green check #2334
Description
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 wheneverCOVERAGE_EVIDENCE_RESULTissuccess, publishes the fallback notice, and lets the job concludesuccess.- The only signals that a model never ran are an
::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:
- 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
Activity
seonghobae commentedon Sep 21, 2026
Duplicate of #2335, which has the more precise diagnosis. Closing this in its favour; #2336 is the fix.
One claim in this issue was wrong, and #2335 is right. I wrote that
opencode_review_receipt_gate.pyalready keeps a fallback out of formal-receipt status. It does so only forAPPROVED:The fallback posts
CHANGES_REQUESTED, which this branch never sees, so it did count as a formal receipt and suppressed the scheduler wake that would have retried a real review. #2335 found that asymmetry; I missed it. (opencode_existing_approval_gate.pydoes filter fallback markers in any state, but that gate only ever judges approvals, so it was never the hole.)The conclusion both issues share stands: merges were not opened by this — the
CHANGES_REQUESTEDdecision and the independent-approval requirement still blocked them. The damage was detection.Two details from here that #2335 does not carry, kept so they aren't lost:
34931908846, jobopencode-review, head91be6442—attempt 1/1 failed with exit 1,OPENCODE_MODEL_POOL_OUTCOME: exhausted,model=none, job concludedsuccess. The same run reportedprovider secrets present: 5 of 5and a gateway preflight ofstatus: ready, which is why this never looked like an outage..github/workflows/opencode-review-dispatch.yml:6954,publish_blockers_after_model_unavailable()— it runs wheneverCOVERAGE_EVIDENCE_RESULTissuccess.The suggestion here to alert on consecutive fallback conclusions is not covered by #2336 and is worth keeping as follow-up: #2336 makes a single outage visible as a red check, but a run-over-run count is what distinguishes a transient provider blip from a three-week outage.
seonghobae commentedon Sep 21, 2026
Superseded by #2335 (diagnosis) and #2336 (fix).