Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3230,7 +3230,7 @@ jobs:
OPENCODE_RUN_TIMEOUT_SECONDS: "600"
OPENCODE_EXPORT_TIMEOUT_SECONDS: "120"
OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "3600"
OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "3600"
OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "2100"
# Keep cycling through the high-sensitivity candidate catalog until
# the retry budget or step timeout is exhausted; a single invalid
# cycle can be all provider formatting noise rather than review
Expand All @@ -3247,12 +3247,15 @@ jobs:
OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS: "3600"
OPENCODE_UNKNOWN_CHANGE_RUN_TIMEOUT_SECONDS: "600"
OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "3600"
OPENCODE_DYNAMIC_RUN_TIMEOUT_CAP_SECONDS: "600"
OPENCODE_DYNAMIC_TOTAL_BUDGET_CAP_SECONDS: "1800"
OPENCODE_DYNAMIC_MAX_CYCLES_CAP: "1"
# 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
# full medium/large cadence slot after the long-context candidate.
OPENCODE_GITHUB_GPT5_RUN_TIMEOUT_SECONDS: "45"
OPENCODE_DYNAMIC_MAX_CYCLES: "0"
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' }}
OPENCODE_CENTRAL_REVIEW_PROCESS_FALLBACK_RUN_TIMEOUT_SECONDS: "600"
Expand Down
34 changes: 34 additions & 0 deletions scripts/ci/run_opencode_review_model_pool.sh
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,38 @@ env_integer_or_default() {
fi
}

cap_dynamic_cadence_for_queue() {
local timeout_cap budget_cap cycle_cap previous_run_timeout previous_budget_seconds previous_max_cycles

timeout_cap="$(env_integer_or_default OPENCODE_DYNAMIC_RUN_TIMEOUT_CAP_SECONDS 600)"
budget_cap="$(env_integer_or_default OPENCODE_DYNAMIC_TOTAL_BUDGET_CAP_SECONDS 1800)"
cycle_cap="$(env_integer_or_default OPENCODE_DYNAMIC_MAX_CYCLES_CAP 1)"
previous_run_timeout="$original_run_timeout"
previous_budget_seconds="$budget_seconds"
previous_max_cycles="$max_cycles"

if [ "$timeout_cap" -gt 0 ] && [ "$original_run_timeout" -gt "$timeout_cap" ]; then
original_run_timeout="$timeout_cap"
fi
if [ "$budget_cap" -gt 0 ] && [ "$budget_seconds" -gt "$budget_cap" ]; then
budget_seconds="$budget_cap"
fi
if [ "$cycle_cap" -gt 0 ]; then
if [ "$max_cycles" -eq 0 ] || [ "$max_cycles" -gt "$cycle_cap" ]; then
max_cycles="$cycle_cap"
fi
fi

if [ "$original_run_timeout" != "$previous_run_timeout" ] ||
[ "$budget_seconds" != "$previous_budget_seconds" ] ||
[ "$max_cycles" != "$previous_max_cycles" ]; then
printf 'OpenCode dynamic review cadence queue cap applied: per-attempt %ss -> %ss, total budget %ss -> %ss, max-cycles %s -> %s; set OPENCODE_DYNAMIC_*_CAP_SECONDS or OPENCODE_DYNAMIC_MAX_CYCLES_CAP to 0 to disable a specific queue cap.\n' \
"$previous_run_timeout" "$original_run_timeout" \
"$previous_budget_seconds" "$budget_seconds" \
"$previous_max_cycles" "$max_cycles"
fi
}

count_changed_files_for_cadence() {
local changed_files_file="${OPENCODE_CHANGED_FILES_FILE:-}"

Expand Down Expand Up @@ -646,12 +678,14 @@ main() {
budget_seconds="$(env_integer_or_default OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS 7200)"
fi
max_cycles="$(env_integer_or_default OPENCODE_DYNAMIC_MAX_CYCLES 0)"
cap_dynamic_cadence_for_queue
printf 'OpenCode dynamic review cadence selected %ss per attempt and %ss total budget for %s changed file(s); max-cycles=%s.\n' \
"$original_run_timeout" "$budget_seconds" "$changed_file_count" "$max_cycles"
else
original_run_timeout="$(env_integer_or_default OPENCODE_UNKNOWN_CHANGE_RUN_TIMEOUT_SECONDS 1800)"
budget_seconds="$(env_integer_or_default OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS 3900)"
max_cycles="$(env_integer_or_default OPENCODE_DYNAMIC_MAX_CYCLES 0)"
cap_dynamic_cadence_for_queue
printf 'OpenCode dynamic review cadence could not read OPENCODE_CHANGED_FILES_FILE; using %ss per attempt and %ss total budget; max-cycles=%s.\n' \
"$original_run_timeout" "$budget_seconds" "$max_cycles"
fi
Expand Down
9 changes: 6 additions & 3 deletions tests/test_opencode_agent_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent():
assert 'OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "3600"' in workflow
assert 'OPENCODE_RUN_TIMEOUT_SECONDS: "600"' in workflow
assert 'OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "3600"' in workflow
assert 'OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "3600"' in workflow
assert 'OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "2100"' in workflow
assert 'timeout --kill-after=30s "${OPENCODE_POOL_STEP_TIMEOUT_SECONDS:-3600}s"' in workflow
assert "OpenCode model pool exceeded the outer" in workflow
assert 'OPENCODE_POOL_MAX_CYCLES: "0"' in workflow
Expand Down Expand Up @@ -750,7 +750,7 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent():
assert 'OPENCODE_RUN_TIMEOUT_SECONDS: "600"' in workflow
assert 'OPENCODE_EXPORT_TIMEOUT_SECONDS: "120"' in workflow
assert 'OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "3600"' in workflow
assert 'OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "3600"' in workflow
assert 'OPENCODE_POOL_STEP_TIMEOUT_SECONDS: "2100"' in workflow
assert 'OPENCODE_POOL_MAX_CYCLES: "0"' in workflow
assert 'OPENCODE_DYNAMIC_REVIEW_CADENCE: "true"' in workflow
assert 'OPENCODE_CHANGED_FILES_FILE: ${{ runner.temp }}/opencode-changed-files.txt' in workflow
Expand All @@ -762,8 +762,11 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent():
assert 'OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS: "3600"' in workflow
assert 'OPENCODE_UNKNOWN_CHANGE_RUN_TIMEOUT_SECONDS: "600"' in workflow
assert 'OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "3600"' in workflow
assert 'OPENCODE_DYNAMIC_RUN_TIMEOUT_CAP_SECONDS: "600"' in workflow
assert 'OPENCODE_DYNAMIC_TOTAL_BUDGET_CAP_SECONDS: "1800"' in workflow
assert 'OPENCODE_DYNAMIC_MAX_CYCLES_CAP: "1"' in workflow
assert 'OPENCODE_GITHUB_GPT5_RUN_TIMEOUT_SECONDS: "45"' in workflow
assert 'OPENCODE_DYNAMIC_MAX_CYCLES: "0"' 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(
" - name: Run merge scheduler after approval", 1
Expand Down
27 changes: 27 additions & 0 deletions tests/test_opencode_model_pool_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,33 @@ def test_dynamic_review_cadence_uses_small_change_timeout(tmp_path: Path) -> Non
assert "retry budget remaining." in result.stdout


def test_dynamic_review_cadence_caps_large_change_queue_budget(tmp_path: Path) -> None:
"""Large PR cadence logs queue caps instead of pinning review jobs for an hour."""
changed_files = [f"backend/changed_{index}.py" for index in range(21)]
result = run_failed_model(
tmp_path,
changed_files=changed_files,
extra_env={
"OPENCODE_DYNAMIC_REVIEW_CADENCE": "true",
"OPENCODE_DYNAMIC_MAX_CYCLES": "0",
"OPENCODE_LARGE_CHANGE_RUN_TIMEOUT_SECONDS": "3600",
"OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS": "7200",
},
model_candidates="github-models/deepseek/deepseek-v3-0324",
)

assert result.returncode == 1
assert (
"OpenCode dynamic review cadence queue cap applied: per-attempt 3600s -> 600s, "
"total budget 7200s -> 1800s, max-cycles 0 -> 1"
) in result.stdout
assert (
"OpenCode dynamic review cadence selected 600s per attempt and 1800s total budget "
"for 21 changed file(s); max-cycles=1."
) in result.stdout
assert "OpenCode model pool reached configured max cycle count 1" in result.stdout


def test_github_gpt5_runtime_cap_preserves_queue_budget(tmp_path: Path) -> None:
"""Known constrained GitHub GPT-5 endpoints cannot consume a full cadence slot."""
result = run_failed_model(
Expand Down
Loading