diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 6d8f91020..e3bbc7fa2 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -1529,10 +1529,10 @@ jobs: ) runs-on: ubuntu-latest # Coverage and current-head evidence are prepared before the model pool. - # The model pool has a 25-minute retry budget and its publish gate has an - # 8-minute cap, so 45 minutes releases genuinely hung runners without - # truncating the bounded multi-provider review path. - timeout-minutes: 45 + # The model pool step is capped at 50 minutes (2880s shell watchdog) and + # its publish gate has an 8-minute cap, so 75 minutes releases genuinely + # hung runners without truncating the bounded multi-provider review path. + timeout-minutes: 75 permissions: actions: read checks: read @@ -3251,7 +3251,7 @@ jobs: - name: Run OpenCode PR Review model pool id: opencode_review_model_pool if: needs.coverage-evidence.result == 'success' - timeout-minutes: 20 + timeout-minutes: 50 continue-on-error: true env: STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} @@ -3285,7 +3285,7 @@ jobs: OPENCODE_RUN_TIMEOUT_SECONDS: "300" OPENCODE_EXPORT_TIMEOUT_SECONDS: "120" OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "600" - OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "1080" + OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "2880" # Visit the high-sensitivity candidate catalog once. Per-provider # failures and timeouts are logged before the publish gate evaluates # the current-head result and fails closed when no verdict exists. @@ -3293,14 +3293,19 @@ jobs: OPENCODE_DYNAMIC_REVIEW_CADENCE: "true" OPENCODE_SMALL_CHANGE_FILE_THRESHOLD: "3" OPENCODE_MEDIUM_CHANGE_FILE_THRESHOLD: "20" - OPENCODE_SMALL_CHANGE_RUN_TIMEOUT_SECONDS: "300" - OPENCODE_SMALL_CHANGE_TOTAL_BUDGET_SECONDS: "600" - OPENCODE_MEDIUM_CHANGE_RUN_TIMEOUT_SECONDS: "420" - OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS: "900" - OPENCODE_LARGE_CHANGE_RUN_TIMEOUT_SECONDS: "420" - OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS: "1080" - OPENCODE_UNKNOWN_CHANGE_RUN_TIMEOUT_SECONDS: "420" - OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "900" + # Cadence sized for the direct-OpenAI slot (gpt-5.6-luna), the only + # candidate producing verdicts while the GitHub Models org budget is + # capped: at the previous 420s/900s a substantive multi-file review + # timed out mid-run and the exhausted pool stalled the org merge + # queue (2026-07-13). The outer watchdog still bounds the step. + OPENCODE_SMALL_CHANGE_RUN_TIMEOUT_SECONDS: "900" + OPENCODE_SMALL_CHANGE_TOTAL_BUDGET_SECONDS: "1500" + OPENCODE_MEDIUM_CHANGE_RUN_TIMEOUT_SECONDS: "1500" + OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS: "2400" + OPENCODE_LARGE_CHANGE_RUN_TIMEOUT_SECONDS: "1800" + OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS: "2700" + OPENCODE_UNKNOWN_CHANGE_RUN_TIMEOUT_SECONDS: "1500" + OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "2400" OPENCODE_DYNAMIC_MAX_CYCLES: "1" CENTRAL_REVIEW_PROCESS_FALLBACK_ELIGIBLE: ${{ steps.central_review_process_fallback_scope.outputs.eligible || 'false' }} CENTRAL_REVIEW_PROCESS_FALLBACK_SCOPE_LABEL: ${{ steps.central_review_process_fallback_scope.outputs.scope_label || 'unsupported' }} @@ -3326,13 +3331,13 @@ jobs: run: | set -euo pipefail set +e - timeout --kill-after=30s "${OPENCODE_POOL_STEP_TIMEOUT_SECONDS:-1080}s" \ + timeout --kill-after=30s "${OPENCODE_POOL_STEP_TIMEOUT_SECONDS:-2880}s" \ bash "$GITHUB_WORKSPACE/scripts/ci/run_opencode_review_model_pool.sh" pool_status=$? set -e if [ "$pool_status" -eq 124 ] || [ "$pool_status" -eq 137 ] || [ "$pool_status" -eq 143 ]; then printf 'OpenCode model pool exceeded the outer %ss step budget; marking the pool exhausted so current-head evidence fallback can publish a bounded reason instead of blocking the org queue.\n' \ - "${OPENCODE_POOL_STEP_TIMEOUT_SECONDS:-1080}" + "${OPENCODE_POOL_STEP_TIMEOUT_SECONDS:-2880}" { printf 'review_model=\n' printf 'review_status=exhausted\n' diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index 0aa8d7332..982c84266 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -569,7 +569,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" "not a generic model-exhaustion message" "opencode review tells models to return concrete missing-evidence findings instead of progress-only output" assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" "tokens_limit_reached" "opencode review detects provider context-window overflow" assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" "skipping remaining attempts for this model" "opencode review skips same-model retries after context-window overflow" - assert_file_contains "$workflow_file" 'timeout-minutes: 45' "opencode review target releases stalled review runners within the bounded queue budget" + assert_file_contains "$workflow_file" 'timeout-minutes: 75' "opencode review target releases stalled review runners within the bounded queue budget" assert_file_contains "$workflow_file" 'timeout-minutes: 12' "opencode evidence preparation fails closed before it ties up the review queue" assert_file_contains "$workflow_file" 'timeout-minutes: 12' "opencode model pool gives multiple candidates a bounded review window while capping stalled model attempts" assert_file_contains "$workflow_file" 'timeout-minutes: 8' "opencode approval publication is bounded so a stalled review releases queue capacity promptly" diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index a38f74c07..bac89957c 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -670,17 +670,17 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert '"## Check outcome"' not in workflow assert "publish REQUEST_CHANGES when coverage-evidence blocker states" in workflow assert re.search(r"Prepare bounded OpenCode review evidence[\s\S]{0,120}timeout-minutes: 12", workflow) - assert re.search(r"opencode-review-target:[\s\S]*?timeout-minutes: 45", workflow) + assert re.search(r"opencode-review-target:[\s\S]*?timeout-minutes: 75", workflow) assert 'timeout-minutes: 12' in workflow - assert re.search(r"Run OpenCode PR Review model pool[\s\S]{0,240}timeout-minutes: 20", workflow) - assert 'OPENCODE_SMALL_CHANGE_TOTAL_BUDGET_SECONDS: "600"' in workflow - assert 'OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS: "900"' in workflow - assert 'OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS: "1080"' in workflow - assert 'OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "900"' in workflow + assert re.search(r"Run OpenCode PR Review model pool[\s\S]{0,240}timeout-minutes: 50", workflow) + assert 'OPENCODE_SMALL_CHANGE_TOTAL_BUDGET_SECONDS: "1500"' in workflow + assert 'OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS: "2400"' in workflow + assert 'OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS: "2700"' in workflow + assert 'OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "2400"' in workflow assert 'OPENCODE_RUN_TIMEOUT_SECONDS: "300"' in workflow assert 'OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "600"' in workflow - assert 'OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "1080"' in workflow - assert 'timeout --kill-after=30s "${OPENCODE_POOL_STEP_TIMEOUT_SECONDS:-1080}s"' in workflow + assert 'OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "2880"' in workflow + assert 'timeout --kill-after=30s "${OPENCODE_POOL_STEP_TIMEOUT_SECONDS:-2880}s"' in workflow assert "OpenCode model pool exceeded the outer" in workflow assert 'OPENCODE_POOL_MAX_CYCLES: "1"' in workflow assert re.search(r"Run OpenCode PR Review model pool[\s\S]{0,280}continue-on-error: true", workflow) @@ -703,18 +703,18 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert 'OPENCODE_RUN_TIMEOUT_SECONDS: "300"' in workflow assert 'OPENCODE_EXPORT_TIMEOUT_SECONDS: "120"' in workflow assert 'OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "600"' in workflow - assert 'OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "1080"' in workflow + assert 'OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "2880"' in workflow assert 'OPENCODE_POOL_MAX_CYCLES: "1"' in workflow assert 'OPENCODE_DYNAMIC_REVIEW_CADENCE: "true"' in workflow assert 'OPENCODE_CHANGED_FILES_FILE: ${{ runner.temp }}/opencode-changed-files.txt' in workflow - assert 'OPENCODE_SMALL_CHANGE_RUN_TIMEOUT_SECONDS: "300"' in workflow - assert 'OPENCODE_SMALL_CHANGE_TOTAL_BUDGET_SECONDS: "600"' in workflow - assert 'OPENCODE_MEDIUM_CHANGE_RUN_TIMEOUT_SECONDS: "420"' in workflow - assert 'OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS: "900"' in workflow - assert 'OPENCODE_LARGE_CHANGE_RUN_TIMEOUT_SECONDS: "420"' in workflow - assert 'OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS: "1080"' in workflow - assert 'OPENCODE_UNKNOWN_CHANGE_RUN_TIMEOUT_SECONDS: "420"' in workflow - assert 'OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "900"' in workflow + assert 'OPENCODE_SMALL_CHANGE_RUN_TIMEOUT_SECONDS: "900"' in workflow + assert 'OPENCODE_SMALL_CHANGE_TOTAL_BUDGET_SECONDS: "1500"' in workflow + assert 'OPENCODE_MEDIUM_CHANGE_RUN_TIMEOUT_SECONDS: "1500"' in workflow + assert 'OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS: "2400"' in workflow + assert 'OPENCODE_LARGE_CHANGE_RUN_TIMEOUT_SECONDS: "1800"' in workflow + assert 'OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS: "2700"' in workflow + assert 'OPENCODE_UNKNOWN_CHANGE_RUN_TIMEOUT_SECONDS: "1500"' in workflow + assert 'OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "2400"' in workflow assert 'OPENCODE_DYNAMIC_MAX_CYCLES: "1"' in workflow assert 'OPENCODE_BACKOFF_MAX_SECONDS: "30"' in workflow publish_step = workflow.split(" - name: Publish OpenCode review outcome", 1)[1].split(