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
50 changes: 38 additions & 12 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,14 @@ jobs:
PY
}

run_python_uv_lock_check() {
local project_dir="$1"
if [ -f "${project_dir}/uv.lock" ]; then
run_and_capture "Python uv lockfile consistency (${project_dir})" \
bash -c 'cd "$1" && uv lock --check' bash "$project_dir"
fi
}

install_python_project_dependencies() {
if [ -f requirements.txt ]; then
run_and_capture "Python project dependencies (requirements.txt)" \
Expand All @@ -505,6 +513,7 @@ jobs:
while IFS= read -r project_dir; do
pyproject_file="${project_dir}/pyproject.toml"
if [ -f "$pyproject_file" ]; then
run_python_uv_lock_check "$project_dir"
if pyproject_has_dev_dependency_group "$pyproject_file"; then
run_and_capture "Python project dependencies (${project_dir})" \
uv sync --project "$project_dir" --group dev
Expand Down Expand Up @@ -1519,7 +1528,11 @@ jobs:
)
)
runs-on: ubuntu-latest
timeout-minutes: 420
# 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
permissions:
actions: read
checks: read
Expand Down Expand Up @@ -3221,7 +3234,7 @@ jobs:
- name: Run OpenCode PR Review model pool
id: opencode_review_model_pool
if: needs.coverage-evidence.result == 'success'
timeout-minutes: 210
timeout-minutes: 30
continue-on-error: true
env:
STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }}
Expand All @@ -3245,17 +3258,29 @@ jobs:
# the SAME model 5x let a rate-limited/hung leader consume the whole
# step, so the pool never reached a healthy fallback model.
OPENCODE_MODEL_ATTEMPTS: "1"
# 90 minutes gives DeepSeek V3 enough room for deep tool-using review.
# The retry budget still bounds queue impact: DeepSeek gets a full
# window, then one strong fallback can run before publish fails closed
# with visible provider/check evidence.
OPENCODE_RUN_TIMEOUT_SECONDS: "5400"
# A provider gets ten minutes for a tool-using review before the pool
# advances by default. Dynamic cadence below shortens small PRs and
# lengthens larger ones only within the 30-minute model step cap, so
# one stalled endpoint cannot occupy the org queue indefinitely.
OPENCODE_RUN_TIMEOUT_SECONDS: "600"
OPENCODE_EXPORT_TIMEOUT_SECONDS: "120"
OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "11400"
# Allow one retry catalog pass for transient provider/model-output
# failures; the retry budget still fails closed with visible
# diagnostics before the 210-minute step timeout.
OPENCODE_POOL_MAX_CYCLES: "2"
OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "1500"
# 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.
OPENCODE_POOL_MAX_CYCLES: "1"
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: "900"
OPENCODE_MEDIUM_CHANGE_RUN_TIMEOUT_SECONDS: "600"
OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS: "1500"
OPENCODE_LARGE_CHANGE_RUN_TIMEOUT_SECONDS: "900"
OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS: "1500"
OPENCODE_UNKNOWN_CHANGE_RUN_TIMEOUT_SECONDS: "600"
OPENCODE_UNKNOWN_CHANGE_TOTAL_BUDGET_SECONDS: "1500"
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: "300"
Expand All @@ -3266,6 +3291,7 @@ jobs:
OPENCODE_FIRST_ATTEMPT_AGENT: ci-review
OPENCODE_AGENT: ci-review-fallback
OPENCODE_EVIDENCE_FILE: ${{ runner.temp }}/opencode-review-evidence.md
OPENCODE_CHANGED_FILES_FILE: ${{ runner.temp }}/opencode-changed-files.txt
OPENCODE_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-model-pool.md
OPENCODE_REVIEW_WORKDIR: ${{ runner.temp }}/opencode-review-project
OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/pr-review-merge-scheduler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ on:
stale_opencode_minutes:
description: Redispatch OpenCode Review when an in-progress OpenCode check is older than this many minutes
required: false
default: "420"
default: "90"
type: string
project_flow:
description: Project flow, usually github-flow or git-flow
Expand Down Expand Up @@ -122,7 +122,7 @@ on:
stale_opencode_minutes:
description: Redispatch OpenCode Review when an in-progress OpenCode check is older than this many minutes
required: false
default: "420"
default: "90"

concurrency:
group: >-
Expand Down Expand Up @@ -195,7 +195,7 @@ jobs:
ENABLE_AUTO_MERGE: ${{ github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'pull_request_target' || github.event_name == 'workflow_run' || inputs.enable_auto_merge == true }}
MERGE_MODE: ${{ inputs.merge_mode || vars.PR_MERGE_MODE || 'direct_or_auto' }}
UPDATE_BRANCHES: ${{ github.event_name == 'schedule' || github.event_name == 'push' || github.event_name == 'pull_request_target' || github.event_name == 'workflow_run' || inputs.update_branches == true }}
STALE_OPENCODE_MINUTES: ${{ inputs.stale_opencode_minutes || vars.STALE_OPENCODE_MINUTES || '420' }}
STALE_OPENCODE_MINUTES: ${{ inputs.stale_opencode_minutes || vars.STALE_OPENCODE_MINUTES || '90' }}
steps:
- name: Exchange OpenCode app token for scheduler mutations
id: scheduler_app_token
Expand Down Expand Up @@ -440,7 +440,7 @@ jobs:
# if MORE than this many repositories become unreachable at once, the whole
# credential likely broke and the job fails loudly.
ORG_SWEEP_MAX_UNAVAILABLE: ${{ vars.ORG_SWEEP_MAX_UNAVAILABLE || '5' }}
STALE_OPENCODE_MINUTES: ${{ inputs.stale_opencode_minutes || vars.STALE_OPENCODE_MINUTES || '420' }}
STALE_OPENCODE_MINUTES: ${{ inputs.stale_opencode_minutes || vars.STALE_OPENCODE_MINUTES || '90' }}
steps:
- name: Exchange OpenCode app token for sweep mutations
id: sweep_app_token
Expand Down
12 changes: 5 additions & 7 deletions scripts/ci/pr_review_merge_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,11 @@
""" + PULL_REQUEST_FIELDS_FRAGMENT

OPEN_PRS_PAGE_SIZE = 25
# Must exceed the opencode-review job timeout (120 min) plus typical runner-queue
# wait. QUEUED counts as running and the age clock starts at check creation, so a
# 45-minute threshold marked every queued/long review "stale" and re-dispatched
# it; each re-dispatch went to the back of the runner queue and itself went
# stale, multiplying load until the org queue saturated. 420 only recovers
# genuinely zombie checks that outlive any legitimate run.
DEFAULT_STALE_OPENCODE_MINUTES = 420
# Must exceed the 45-minute OpenCode job cap plus typical runner-queue wait.
# QUEUED counts as running and the age clock starts at check creation, so this
# remains deliberately larger than the job cap while recovering genuine zombie
# checks in the same operating window instead of leaving them for seven hours.
DEFAULT_STALE_OPENCODE_MINUTES = 90
DEFAULT_UPDATE_BRANCH_HEAD_POLL_ATTEMPTS = 6
DEFAULT_UPDATE_BRANCH_HEAD_POLL_SECONDS = 5.0
OPENCODE_WORKFLOW_NAMES = {"OpenCode Review", "Required OpenCode Review"}
Expand Down
59 changes: 56 additions & 3 deletions scripts/ci/run_opencode_review_model_pool.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,34 @@ backoff_sleep() {
printf '%s\n' "$sleep_for"
}

is_non_negative_integer() {
case "${1:-}" in
"" | *[!0-9]*) return 1 ;;
*) return 0 ;;
esac
}

env_integer_or_default() {
local name="$1"
local default_value="$2"
local value="${!name:-}"

if is_non_negative_integer "$value"; then
printf '%s\n' "$value"
else
printf '%s\n' "$default_value"
fi
}

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

if [ -z "$changed_files_file" ] || [ ! -f "$changed_files_file" ]; then
return 1
fi
awk 'NF { count += 1 } END { printf "%d\n", count + 0 }' "$changed_files_file"
}

should_inline_prompt_evidence_excerpt() {
local model_candidate="$1"

Expand Down Expand Up @@ -290,7 +318,7 @@ run_one_model_attempt() {
local opencode_export_file="$8"
local run_timeout_seconds export_timeout_seconds opencode_status session_id opencode_stderr_file

run_timeout_seconds="${OPENCODE_RUN_TIMEOUT_SECONDS:-5400}"
run_timeout_seconds="${OPENCODE_RUN_TIMEOUT_SECONDS:-600}"
export_timeout_seconds="${OPENCODE_EXPORT_TIMEOUT_SECONDS:-120}"
opencode_stderr_file="${opencode_json_file}.stderr"

Expand Down Expand Up @@ -349,18 +377,43 @@ run_one_model_attempt() {
main() {
local attempts budget_seconds deadline now remaining model_candidate attempt safe_model prompt_file candidate_output_file
local opencode_json_file opencode_export_file agent retry_sleep original_run_timeout run_status cycle_sleep cycle max_cycles
local changed_file_count small_file_threshold medium_file_threshold
local -a model_candidates

attempts="${OPENCODE_MODEL_ATTEMPTS:-3}"
original_run_timeout="${OPENCODE_RUN_TIMEOUT_SECONDS:-5400}"
budget_seconds="${OPENCODE_TOTAL_RETRY_BUDGET_SECONDS:-11400}"
original_run_timeout="${OPENCODE_RUN_TIMEOUT_SECONDS:-600}"
budget_seconds="${OPENCODE_TOTAL_RETRY_BUDGET_SECONDS:-1500}"
max_cycles="${OPENCODE_POOL_MAX_CYCLES:-0}"
if [ "${CENTRAL_REVIEW_PROCESS_FALLBACK_ELIGIBLE:-false}" = "true" ]; then
original_run_timeout="${OPENCODE_CENTRAL_REVIEW_PROCESS_FALLBACK_RUN_TIMEOUT_SECONDS:-300}"
budget_seconds="${OPENCODE_CENTRAL_REVIEW_PROCESS_FALLBACK_TOTAL_BUDGET_SECONDS:-420}"
max_cycles="${OPENCODE_CENTRAL_REVIEW_PROCESS_FALLBACK_MAX_CYCLES:-1}"
printf 'Central review-process evidence fallback eligible for scope "%s"; limiting OpenCode model pool to %ss per attempt, %ss total budget, and %s cycle(s) so provider delay is logged before the publish fallback evaluates current-head peer evidence.\n' \
"${CENTRAL_REVIEW_PROCESS_FALLBACK_SCOPE_LABEL:-unsupported}" "$original_run_timeout" "$budget_seconds" "$max_cycles"
elif [ "${OPENCODE_DYNAMIC_REVIEW_CADENCE:-false}" = "true" ]; then
small_file_threshold="$(env_integer_or_default OPENCODE_SMALL_CHANGE_FILE_THRESHOLD 3)"
medium_file_threshold="$(env_integer_or_default OPENCODE_MEDIUM_CHANGE_FILE_THRESHOLD 20)"
if changed_file_count="$(count_changed_files_for_cadence)"; then
if [ "$changed_file_count" -le "$small_file_threshold" ]; then
original_run_timeout="$(env_integer_or_default OPENCODE_SMALL_CHANGE_RUN_TIMEOUT_SECONDS 900)"
budget_seconds="$(env_integer_or_default OPENCODE_SMALL_CHANGE_TOTAL_BUDGET_SECONDS 2100)"
elif [ "$changed_file_count" -le "$medium_file_threshold" ]; then
original_run_timeout="$(env_integer_or_default OPENCODE_MEDIUM_CHANGE_RUN_TIMEOUT_SECONDS 1800)"
budget_seconds="$(env_integer_or_default OPENCODE_MEDIUM_CHANGE_TOTAL_BUDGET_SECONDS 3900)"
else
original_run_timeout="$(env_integer_or_default OPENCODE_LARGE_CHANGE_RUN_TIMEOUT_SECONDS 3600)"
budget_seconds="$(env_integer_or_default OPENCODE_LARGE_CHANGE_TOTAL_BUDGET_SECONDS 7200)"
fi
max_cycles="$(env_integer_or_default OPENCODE_DYNAMIC_MAX_CYCLES 1)"
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 1)"
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
fi
deadline=0
if [ "$budget_seconds" -gt 0 ]; then
Expand Down
Loading
Loading