From 601fb6410f800e893597a42719bc73509a86f0e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 3 Aug 2026 21:44:08 +0900 Subject: [PATCH 1/5] fix(ci): bound anonymous review provider budget --- .../workflows/opencode-review-dispatch.yml | 4 ++ scripts/ci/run_opencode_review_model_pool.sh | 59 +++++++++++++++++-- scripts/ci/test_strix_quick_gate.sh | 4 +- tests/test_opencode_agent_contract.py | 1 + tests/test_opencode_model_pool_runner.py | 30 ++++++++++ 5 files changed, 91 insertions(+), 7 deletions(-) diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index d826ce67a..1b755fa8f 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -4342,6 +4342,10 @@ jobs: OPENCODE_NVIDIA_NIM_RUN_TIMEOUT_SECONDS: "180" OPENCODE_NVIDIA_NIM_TOTAL_BUDGET_SECONDS: "900" OPENCODE_FREE_RUN_TIMEOUT_SECONDS: "3600" + # Anonymous free candidates share fifteen minutes total. A quota-starved + # catalog therefore cannot consume the entire 195-minute retry budget + # before keyed Terra/OpenAI/OpenRouter/GitHub Models fallbacks run. + OPENCODE_FREE_TOTAL_BUDGET_SECONDS: "900" # This installation currently reports a 4k request-body limit for # GitHub Models GPT-5 endpoints even though the public catalog is # larger. Keep the exact runtime failure visible without spending a diff --git a/scripts/ci/run_opencode_review_model_pool.sh b/scripts/ci/run_opencode_review_model_pool.sh index 986982e9a..c03930005 100644 --- a/scripts/ci/run_opencode_review_model_pool.sh +++ b/scripts/ci/run_opencode_review_model_pool.sh @@ -368,6 +368,13 @@ is_nvidia_nim_candidate() { esac } +is_opencode_free_candidate() { + case "$1" in + opencode-free/*) return 0 ;; + *) return 1 ;; + esac +} + is_schema_repair_candidate() { case "$1" in nvidia-nim/* | opencode-free/*) return 0 ;; @@ -548,7 +555,8 @@ main() { local changed_file_count small_file_threshold medium_file_threshold local invalid_control_cap max_total_attempts total_attempts alive_candidates local nim_budget_seconds nim_elapsed_seconds nim_remaining_seconds - local nim_attempt_started nim_attempt_elapsed non_nim_candidate_count + local free_budget_seconds free_elapsed_seconds free_remaining_seconds + local attempt_started attempt_elapsed non_nim_candidate_count keyed_candidate_count local -A dead_candidate_reasons invalid_control_counts local -a model_candidates @@ -615,11 +623,18 @@ main() { fi nim_budget_seconds="$(env_integer_or_default OPENCODE_NVIDIA_NIM_TOTAL_BUDGET_SECONDS 900)" nim_elapsed_seconds=0 + free_budget_seconds="$(env_integer_or_default OPENCODE_FREE_TOTAL_BUDGET_SECONDS 900)" + free_elapsed_seconds=0 non_nim_candidate_count=0 + keyed_candidate_count=0 for model_candidate in "${model_candidates[@]}"; do if ! is_nvidia_nim_candidate "$model_candidate"; then non_nim_candidate_count=$((non_nim_candidate_count + 1)) fi + if ! is_nvidia_nim_candidate "$model_candidate" && + ! is_opencode_free_candidate "$model_candidate"; then + keyed_candidate_count=$((keyed_candidate_count + 1)) + fi done if [ "$non_nim_candidate_count" -gt 0 ] && [ "$budget_seconds" -gt 0 ] && @@ -628,8 +643,15 @@ main() { printf 'OpenCode NVIDIA NIM combined runtime budget was capped at %ss so %s non-NIM fallback candidate(s) retain retry budget.\n' \ "$nim_budget_seconds" "$non_nim_candidate_count" fi - printf 'Configured OpenCode model pool: candidates=%s attempts=%s per-model-timeout=%ss retry-budget=%ss max-cycles=%s NVIDIA-NIM-combined-budget=%ss.\n' \ - "${#model_candidates[@]}" "$attempts" "$original_run_timeout" "$budget_seconds" "$max_cycles" "$nim_budget_seconds" + if [ "$keyed_candidate_count" -gt 0 ] && + [ "$budget_seconds" -gt 0 ] && + [ "$free_budget_seconds" -ge "$budget_seconds" ]; then + free_budget_seconds=$((budget_seconds / 2)) + printf 'OpenCode anonymous-free combined runtime budget was capped at %ss so %s keyed fallback candidate(s) retain retry budget.\n' \ + "$free_budget_seconds" "$keyed_candidate_count" + fi + printf 'Configured OpenCode model pool: candidates=%s attempts=%s per-model-timeout=%ss retry-budget=%ss max-cycles=%s NVIDIA-NIM-combined-budget=%ss anonymous-free-combined-budget=%ss.\n' \ + "${#model_candidates[@]}" "$attempts" "$original_run_timeout" "$budget_seconds" "$max_cycles" "$nim_budget_seconds" "$free_budget_seconds" cycle=1 while :; do @@ -649,6 +671,12 @@ main() { "$model_candidate" "$nim_budget_seconds" continue fi + if is_opencode_free_candidate "$model_candidate" && + [ "$free_elapsed_seconds" -ge "$free_budget_seconds" ]; then + printf 'Skipping OpenCode %s because the anonymous-free combined runtime budget of %ss is exhausted; preserving the remaining retry budget for keyed fallback candidates.\n' \ + "$model_candidate" "$free_budget_seconds" + continue + fi assert_reasoning_effort_for_candidate "$model_candidate" safe_model="${model_candidate//[\/:]/-}" prompt_file="${RUNNER_TEMP}/opencode-review-${safe_model}-prompt.md" @@ -673,6 +701,12 @@ main() { "$model_candidate" "$nim_budget_seconds" break fi + if is_opencode_free_candidate "$model_candidate" && + [ "$free_elapsed_seconds" -ge "$free_budget_seconds" ]; then + printf 'Stopping OpenCode %s retries because the anonymous-free combined runtime budget of %ss is exhausted.\n' \ + "$model_candidate" "$free_budget_seconds" + break + fi if [ "$deadline" -gt 0 ] && [ "$now" -ge "$deadline" ]; then printf 'OpenCode model pool retry deadline elapsed before %s attempt %s/%s.\n' "$model_candidate" "$attempt" "$effective_attempts" if finish_pool_without_model; then @@ -704,6 +738,14 @@ main() { OPENCODE_RUN_TIMEOUT_SECONDS="$nim_remaining_seconds" fi fi + if is_opencode_free_candidate "$model_candidate"; then + free_remaining_seconds=$((free_budget_seconds - free_elapsed_seconds)) + if [ "$OPENCODE_RUN_TIMEOUT_SECONDS" -gt "$free_remaining_seconds" ]; then + printf 'OpenCode %s combined anonymous-free budget cap selected %ss instead of %ss so keyed fallback candidates retain retry budget.\n' \ + "$model_candidate" "$free_remaining_seconds" "$OPENCODE_RUN_TIMEOUT_SECONDS" + OPENCODE_RUN_TIMEOUT_SECONDS="$free_remaining_seconds" + fi + fi uncapped_run_timeout="$OPENCODE_RUN_TIMEOUT_SECONDS" OPENCODE_RUN_TIMEOUT_SECONDS="$(cap_model_run_timeout "$model_candidate" "$OPENCODE_RUN_TIMEOUT_SECONDS")" if [ "$OPENCODE_RUN_TIMEOUT_SECONDS" -lt "$uncapped_run_timeout" ]; then @@ -717,7 +759,7 @@ main() { agent="$OPENCODE_FIRST_ATTEMPT_AGENT" fi run_status=0 - nim_attempt_started="$SECONDS" + attempt_started="$SECONDS" if run_one_model_attempt "$model_candidate" "$attempt" "$effective_attempts" "$agent" "$prompt_file" "$candidate_output_file" "$opencode_json_file" "$opencode_export_file"; then cp "$candidate_output_file" "$OPENCODE_OUTPUT_FILE" record_review_model "$model_candidate" @@ -726,12 +768,17 @@ main() { else run_status=$? fi + attempt_elapsed=$((SECONDS - attempt_started)) if is_nvidia_nim_candidate "$model_candidate"; then - nim_attempt_elapsed=$((SECONDS - nim_attempt_started)) - nim_elapsed_seconds=$((nim_elapsed_seconds + nim_attempt_elapsed)) + nim_elapsed_seconds=$((nim_elapsed_seconds + attempt_elapsed)) printf 'OpenCode NVIDIA NIM combined runtime used %ss/%ss after %s attempt %s/%s.\n' \ "$nim_elapsed_seconds" "$nim_budget_seconds" "$model_candidate" "$attempt" "$effective_attempts" fi + if is_opencode_free_candidate "$model_candidate"; then + free_elapsed_seconds=$((free_elapsed_seconds + attempt_elapsed)) + printf 'OpenCode anonymous-free combined runtime used %ss/%ss after %s attempt %s/%s.\n' \ + "$free_elapsed_seconds" "$free_budget_seconds" "$model_candidate" "$attempt" "$effective_attempts" + fi if [ "$run_status" -ne 3 ] && is_credit_exhausted_failure "$opencode_json_file" "${opencode_json_file}.stderr"; then dead_candidate_reasons[$model_candidate]="provider credits exhausted (HTTP 402 / payment required)" printf 'OpenCode %s provider credits are exhausted; marking this candidate failed for the rest of the run so retries cannot accrue further spend.\n' "$model_candidate" diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index b4d585b9e..21a239f9e 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -192,7 +192,7 @@ assert_strix_workflow_pr_trigger_hardened() { assert_equals "1" "$status_token_count" "strix workflow defines GITHUB_STATUS_TOKEN once so GitHub can parse repository_dispatch" assert_file_not_contains "$workflow_file" "github.event.pull_request.number == 240" "strix workflow must not hard-code repository-specific PR bypasses" assert_file_contains "$workflow_file" "models: read" "strix workflow grants only the GitHub Models read permission needed for Strix" - assert_file_contains "$workflow_file" "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6" "strix workflow pins actions/setup-python" + assert_file_contains "$workflow_file" "actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0" "strix workflow pins actions/setup-python" assert_file_contains "$workflow_file" 'python-version: "3.13"' "strix workflow runs Python steps on Python 3.13" assert_file_contains "$workflow_file" "Resolve trusted Strix source ref" "strix workflow resolves the central trusted Strix source ref" assert_file_contains "$workflow_file" "toJSON(job)" "strix workflow derives the trusted source from the job workflow context" @@ -734,11 +734,13 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" 'continue-on-error: true' "opencode approval gate still runs after model-pool failure to publish a reason" assert_file_contains "$workflow_file" 'OPENCODE_RUN_TIMEOUT_SECONDS: "5400"' "opencode primary review preserves legitimate full-hour provider sessions" assert_file_contains "$workflow_file" 'OPENCODE_FREE_RUN_TIMEOUT_SECONDS: "3600"' "opencode free-tier failover timeout is hour-class (~3600s)" +assert_file_contains "$workflow_file" 'OPENCODE_FREE_TOTAL_BUDGET_SECONDS: "900"' "opencode anonymous free candidates share a fifteen-minute combined queue budget" assert_file_contains "$workflow_file" 'OPENCODE_NVIDIA_NIM_RUN_TIMEOUT_SECONDS: "180"' "opencode NVIDIA NIM candidates have a short per-candidate failover timeout" assert_file_contains "$workflow_file" 'OPENCODE_NVIDIA_NIM_TOTAL_BUDGET_SECONDS: "900"' "opencode NVIDIA NIM candidates share a bounded combined runtime budget" assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_RUN_TIMEOUT_SECONDS:-3600' "opencode pool defaults primary run timeout to hour-class (~3600s) for large repos" assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_DYNAMIC_RUN_TIMEOUT_CAP_SECONDS 3600' "opencode pool dynamic timeout cap defaults to hour-class (~3600s)" assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_FREE_RUN_TIMEOUT_SECONDS 3600' "opencode free-tier failover timeout is hour-class (~3600s)" +assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_FREE_TOTAL_BUDGET_SECONDS 900' "opencode pool defaults anonymous free candidates to a fifteen-minute combined queue budget" assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_NVIDIA_NIM_RUN_TIMEOUT_SECONDS 180' "opencode NVIDIA NIM candidate runtime cap defaults to three minutes" assert_file_contains "$REPO_ROOT/scripts/ci/run_opencode_review_model_pool.sh" 'OPENCODE_NVIDIA_NIM_TOTAL_BUDGET_SECONDS 900' "opencode NVIDIA NIM combined runtime cap defaults to fifteen minutes" diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 565ea4b9a..ade9d7501 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -1589,6 +1589,7 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert 'OPENCODE_NVIDIA_NIM_RUN_TIMEOUT_SECONDS: "180"' in workflow assert 'OPENCODE_NVIDIA_NIM_TOTAL_BUDGET_SECONDS: "900"' in workflow assert 'OPENCODE_FREE_RUN_TIMEOUT_SECONDS: "3600"' in workflow + assert 'OPENCODE_FREE_TOTAL_BUDGET_SECONDS: "900"' in workflow assert 'OPENCODE_GITHUB_GPT5_RUN_TIMEOUT_SECONDS: "45"' in workflow assert 'OPENCODE_DYNAMIC_MAX_CYCLES: "1"' in workflow assert 'OPENCODE_BACKOFF_MAX_SECONDS: "30"' in workflow diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index 08d17f000..62df6440b 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -818,6 +818,36 @@ def test_free_provider_runtime_cap_preserves_queue_budget(tmp_path: Path) -> Non ) in result.stdout +def test_free_provider_combined_budget_preserves_keyed_fallback_attempt( + tmp_path: Path, +) -> None: + """Timed-out anonymous providers cannot consume the keyed fallback budget.""" + result = run_failed_model( + tmp_path, + extra_env={ + "FAKE_OPENCODE_HANG_SECONDS": "2", + "OPENCODE_FREE_RUN_TIMEOUT_SECONDS": "3", + "OPENCODE_FREE_TOTAL_BUDGET_SECONDS": "1", + "OPENCODE_RUN_TIMEOUT_SECONDS": "5", + "OPENCODE_TOTAL_RETRY_BUDGET_SECONDS": "15", + }, + model_candidates=( + "opencode-free/nemotron-3-ultra-free " + "opencode-free/deepseek-v4-flash-free " + "github-models/openai/gpt-5" + ), + ) + + assert result.returncode == 1 + assert "OpenCode anonymous-free combined runtime used" in result.stdout + assert ( + "Skipping OpenCode opencode-free/deepseek-v4-flash-free because the " + "anonymous-free combined runtime budget of 1s is exhausted" + in result.stdout + ) + assert "OpenCode github-models/openai/gpt-5 attempt 1/1" in result.stdout + + def test_nvidia_nim_candidate_requires_key( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: From 14baf5438f704d55bdc3a69a06276d9e37144bb3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 01:43:00 +0900 Subject: [PATCH 2/5] fix(ci): align scheduled CodeQL action refs --- .github/workflows/scheduled-security-scan.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scheduled-security-scan.yml b/.github/workflows/scheduled-security-scan.yml index 8ecb5185b..6b19cf257 100644 --- a/.github/workflows/scheduled-security-scan.yml +++ b/.github/workflows/scheduled-security-scan.yml @@ -90,13 +90,13 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis continue-on-error: true - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 with: category: "/language:${{ matrix.language }}-scheduled" From 851e68685e836be34f7ea6871cfcd85dd60c2e7d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 4 Aug 2026 03:31:04 +0900 Subject: [PATCH 3/5] fix(ci): feed canonical validator feedback to review repair --- scripts/ci/run_opencode_review_model_pool.sh | 57 +++++++++++-- tests/test_opencode_model_pool_runner.py | 85 +++++++++++++++++--- 2 files changed, 127 insertions(+), 15 deletions(-) diff --git a/scripts/ci/run_opencode_review_model_pool.sh b/scripts/ci/run_opencode_review_model_pool.sh index c03930005..d07ca0115 100644 --- a/scripts/ci/run_opencode_review_model_pool.sh +++ b/scripts/ci/run_opencode_review_model_pool.sh @@ -3,6 +3,34 @@ set -euo pipefail : "${GITHUB_OUTPUT:=/dev/null}" +# Only canonical, repository-owned validator categories may be replayed into a +# repair prompt. Provider output is untrusted and must never be reflected back +# verbatim as instructions. +LAST_CONTROL_REJECTION_KIND="" + +classify_control_rejection() { + local diagnostics_file="$1" + + LAST_CONTROL_REJECTION_KIND="unknown-control-contract" + if grep -Fq "no top-level current-run control JSON object was found" "$diagnostics_file"; then + LAST_CONTROL_REJECTION_KIND="missing-current-run-control-envelope" + elif grep -Fq "expected exactly one top-level current-run control JSON object" "$diagnostics_file"; then + LAST_CONTROL_REJECTION_KIND="multiple-current-run-control-objects" + elif grep -Fq "must cite the exact probe path and positive line" "$diagnostics_file"; then + LAST_CONTROL_REJECTION_KIND="probe-path-line-mismatch" + elif grep -Fq "must state the observed proof result" "$diagnostics_file"; then + LAST_CONTROL_REJECTION_KIND="probe-observed-result-missing" + elif grep -Fq "must cite an executed command, test/assertion, log/check/SARIF receipt, source trace, diff, or CodeGraph path" "$diagnostics_file"; then + LAST_CONTROL_REJECTION_KIND="probe-proof-anchor-missing" + elif grep -Fq "source-line-sha256 receipt" "$diagnostics_file"; then + LAST_CONTROL_REJECTION_KIND="probe-source-line-receipt-invalid" + elif grep -Fq "approval does not prove 100% coverage or an explicit no-source exception" "$diagnostics_file"; then + LAST_CONTROL_REJECTION_KIND="approval-coverage-proof-missing" + elif grep -Fq "adversarial_validation" "$diagnostics_file"; then + LAST_CONTROL_REJECTION_KIND="adversarial-validation-contract" + fi +} + record_review_status() { printf 'review_status=%s\n' "$1" >>"$GITHUB_OUTPUT" } @@ -35,19 +63,28 @@ normalize_opencode_output() { # copy, then normalize — so the pool only records success for output the # publish step will accept, and leave output_file pristine for the publish # step to normalize itself. - local probe rc + local probe rc diagnostics_file probe="$(mktemp)" + diagnostics_file="$(mktemp)" perl -pe 's/\x1b\[[0-9;?]*[A-Za-z]//g' "$output_file" >"$probe" 2>/dev/null || cp "$output_file" "$probe" if python3 "$GITHUB_WORKSPACE/scripts/ci/opencode_review_normalize_output.py" \ - "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$probe"; then + "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$probe" 2>"$diagnostics_file"; then bash "$GITHUB_WORKSPACE/scripts/ci/opencode_review_approve_gate.sh" \ - "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$probe" >/dev/null + "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" "$probe" >/dev/null 2>>"$diagnostics_file" rc=$? else rc=1 fi - rm -f "$probe" + if [ -s "$diagnostics_file" ]; then + cat "$diagnostics_file" >&2 + fi + if [ "$rc" -ne 0 ]; then + classify_control_rejection "$diagnostics_file" + else + LAST_CONTROL_REJECTION_KIND="" + fi + rm -f "$probe" "$diagnostics_file" return "$rc" } @@ -225,14 +262,20 @@ PY write_schema_repair_prompt() { local model_candidate="$1" local prompt_file="$2" + local rejection_kind="${LAST_CONTROL_REJECTION_KIND:-unknown-control-contract}" write_prompt "$model_candidate" "$prompt_file" { printf '\nA previous response from this same provider reached the trusted validator but failed the control schema. Perform the review again from the same trusted evidence and return one corrected review body only.\n' + printf 'The trusted validator classified the previous rejection as `%s`. This fixed category contains no provider-controlled text.\n' "$rejection_kind" printf 'This is a schema repair opportunity, not permission to weaken, omit, or fabricate evidence. Check every item before returning:\n' - printf -- '- Emit exactly one sentinel and exactly one current-run JSON control object; do not quote any example object or earlier response.\n' + printf -- '- The first output line must be exactly ``; emit no prose or Markdown before it.\n' "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" + printf -- '- After that sentinel, emit exactly one ``; do not quote any example object or earlier response.\n' + printf -- '- The JSON identity values must be head_sha=%s, run_id=%s, and run_attempt=%s.\n' "$HEAD_SHA" "$RUN_ID" "$RUN_ATTEMPT" printf -- '- Choose exactly APPROVE or REQUEST_CHANGES, with a non-empty reason, summary, and residual_risk.\n' printf -- '- Include "adversarial_validation" as an object with at least the required probe count. Copy each path, line, and source-line-sha256 receipt exactly from trusted bounded evidence.\n' + printf -- '- For every probe, repeat its exact `path:line` inside evidence; name a concrete test, assertion, check, log, source trace, diff, or CodeGraph path; state the observed passed, failed, rejected, returned, showed, or exit-code result; include exactly one matching source-line-sha256 receipt.\n' + printf -- '- In an APPROVE summary, include both `Coverage:` and `Docstring coverage:` and cite the exact trusted Coverage execution evidence that supported repository test suites passed and configured docstring gates passed or were advisory, or the exact explicit no-source exception.\n' printf -- '- APPROVE requires status=passed, every probe outcome=falsified, and findings=[].\n' printf -- '- REQUEST_CHANGES requires status=failed, at least one outcome=confirmed, and a non-empty source-backed finding at the same path and line.\n' printf 'Return only the corrected review body now.\n' @@ -852,4 +895,6 @@ main() { done } -main "$@" +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + main "$@" +fi diff --git a/tests/test_opencode_model_pool_runner.py b/tests/test_opencode_model_pool_runner.py index 62df6440b..78f1d6e21 100644 --- a/tests/test_opencode_model_pool_runner.py +++ b/tests/test_opencode_model_pool_runner.py @@ -645,7 +645,9 @@ def test_credit_exhausted_402_ends_pool_without_further_spend(tmp_path: Path) -> assert result.returncode == 1 assert "provider credits are exhausted" in result.stdout assert "marking this candidate failed for the rest of the run" in result.stdout - assert "Every OpenCode model candidate is marked failed for this run" in result.stdout + assert ( + "Every OpenCode model candidate is marked failed for this run" in result.stdout + ) assert "class=credit-exhausted" in result.stdout assert "Restarting OpenCode model pool" not in result.stdout assert elapsed < 20 @@ -680,7 +682,9 @@ def test_invalid_control_output_cap_marks_candidate_failed(tmp_path: Path) -> No assert result.returncode == 1 assert "produced 2 control-rejected outputs" in result.stdout assert "marking this candidate failed for the rest of the run" in result.stdout - assert "Every OpenCode model candidate is marked failed for this run" in result.stdout + assert ( + "Every OpenCode model candidate is marked failed for this run" in result.stdout + ) assert "attempt 3/3" not in result.stdout @@ -697,9 +701,7 @@ def test_attempt_ceiling_bounds_provider_spend(tmp_path: Path) -> None: ) assert result.returncode == 1 - assert ( - "reached the per-run provider attempt ceiling of 2 attempts" in result.stdout - ) + assert "reached the per-run provider attempt ceiling of 2 attempts" in result.stdout assert "attempt 3/3" not in result.stdout @@ -761,7 +763,8 @@ def test_dynamic_review_cadence_caps_large_change_queue_budget(tmp_path: Path) - ) in result.stdout or ( "total budget 7200s -> 1s" in result.stdout and "OpenCode dynamic review cadence selected 3600s per attempt and 1s total budget " - "for 21 changed file(s); max-cycles=0." in result.stdout + "for 21 changed file(s); max-cycles=0." + in result.stdout ) assert ( "OpenCode dynamic review cadence selected 3600s per attempt and 1s total budget " @@ -842,8 +845,7 @@ def test_free_provider_combined_budget_preserves_keyed_fallback_attempt( assert "OpenCode anonymous-free combined runtime used" in result.stdout assert ( "Skipping OpenCode opencode-free/deepseek-v4-flash-free because the " - "anonymous-free combined runtime budget of 1s is exhausted" - in result.stdout + "anonymous-free combined runtime budget of 1s is exhausted" in result.stdout ) assert "OpenCode github-models/openai/gpt-5 attempt 1/1" in result.stdout @@ -996,7 +998,72 @@ def test_free_provider_gets_one_bounded_schema_repair_attempt( assert "exponential backoff" not in result.stdout repair_prompt = prompt_capture.read_text(encoding="utf-8") assert "failed the control schema" in repair_prompt - assert "exactly one sentinel and exactly one current-run JSON control object" in repair_prompt + assert "missing-current-run-control-envelope" in repair_prompt + assert ( + "" in repair_prompt + ) + assert "exactly one `