From 353f5a67342f5758863cbe93df2db4bdc91d5362 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 17:44:20 +0900 Subject: [PATCH] fix(opencode-review): bound verdict-polling loop by wall clock, not just transport-failure count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Fail closed without a current-head OpenCode verdict" step's polling loop was bounded only by max_poll_transport_failures (consecutive gh api transport failures) with no total wall-clock cap of its own. When a review dispatch never produces a verdict while every individual gh api call keeps succeeding, the loop polled forever, holding a live GitHub Actions runner for up to the platform's 360-minute default job timeout. Confirmed live in production: multiple "Required OpenCode Review"/"Strix Security Scan" runs stuck in this exact step for 7-20 hours (e.g. run 33509949967 on bandscope#1115 stuck 1190+ minutes), consuming enough of the org's shared Actions concurrent-job capacity to stall required-review dispatch for essentially every other open PR (thousands of queued runs across .github, contextual-orchestrator, naruon, and other repos). Adds a 3-hour (10800s) wall-clock deadline check at the top of each poll iteration -- comfortably above this org's own documented "accommodate over 2 hours per model" allowance (docs/product-goal-directive.md §8) so a legitimately slow model is never falsely failed, but well short of GitHub's 360-minute job default so a runner is reliably released. This bounds how long the CI job waits for a verdict; it does not cap the model's own reasoning/streaming time, which remains governed entirely upstream. Verified directly: extracted the real step body via PyYAML (matching this file's own existing test extraction pattern) and executed it against a stubbed gh CLI with bash 5. A never-resolving verdict now exits cleanly with a clear diagnostic at exactly the deadline instead of hanging; a verdict posted immediately still succeeds normally and is unaffected by the new check. This is an emergency direct fix authorized by the repository owner given the ongoing org-wide capacity incident (a genuine chicken-and-egg situation: this fix's own required review cannot complete because the system it fixes is what is broken). Co-Authored-By: Claude Sonnet 5 --- .github/workflows/opencode-review.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 48592f0163..a7415cee22 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -423,7 +423,25 @@ jobs: review_poll_failures=0 max_poll_transport_failures=3 poll_interval_seconds=60 + # Wall-clock backstop, distinct from max_poll_transport_failures above: + # that counter only bounds *consecutive transport failures*, so a + # review dispatch that never produces a verdict -- while every + # individual `gh api` call keeps succeeding -- previously polled + # forever, holding a live runner for up to GitHub's 360-minute + # platform default job timeout. 10800s (3h) is chosen to stay + # comfortably above this org's own documented "accommodate over 2 + # hours per model" allowance (docs/product-goal-directive.md §8) + # while still releasing the runner well before the platform + # default. This bounds how long the CI job waits for a verdict; it + # does not cap the model's own reasoning/streaming time, which + # remains governed entirely upstream by the dispatched review run + # itself. + poll_deadline_epoch=$(( $(date -u +%s) + 10800 )) while :; do + if [ "$(date -u +%s)" -ge "$poll_deadline_epoch" ]; then + echo "::error::No current-head OpenCode verdict after 180 minutes of polling; failing closed and releasing the runner." + exit 1 + fi if ! live_poll_pr="$(timeout 30s gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}")"; then live_poll_failures=$((live_poll_failures + 1)) if [ "$live_poll_failures" -ge "$max_poll_transport_failures" ]; then