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
27 changes: 16 additions & 11 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3650,7 +3650,10 @@ jobs:
&& steps.opencode_review_model_pool.outputs.review_status == 'success'
&& steps.central_review_process_fallback_scope.outputs.eligible == 'true'
continue-on-error: true
timeout-minutes: 3
# Some target repositories run multi-language CodeQL, Strix, or R CMD
# checks for several minutes. Keep this step alive long enough for the
# bounded six-minute peer-check window plus publication overhead.
timeout-minutes: 8
env:
GH_TOKEN: ${{ steps.opencode_app_token.outputs.token }}
CHECK_LOOKUP_GH_TOKEN: ${{ github.token }}
Expand All @@ -3669,7 +3672,7 @@ jobs:
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }}
CENTRAL_REVIEW_PROCESS_FALLBACK_CHANGED_COUNT: ${{ steps.central_review_process_fallback_scope.outputs.changed_count || '0' }}
CENTRAL_REVIEW_PROCESS_FALLBACK_SCOPE_LABEL: ${{ steps.central_review_process_fallback_scope.outputs.scope_label || 'unsupported' }}
APPROVAL_CHECK_WAIT_ATTEMPTS: "12"
APPROVAL_CHECK_WAIT_ATTEMPTS: "36"
APPROVAL_CHECK_WAIT_SLEEP_SECONDS: "10"
REVIEW_PUBLISH_GH_API_TIMEOUT_SECONDS: "15"
run: |
Expand Down Expand Up @@ -3757,7 +3760,7 @@ jobs:
check_runs_file="$(mktemp)"
pending_checks_file="$(mktemp)"
failed_checks_file="$(mktemp)"
for attempt in $(seq 1 "${APPROVAL_CHECK_WAIT_ATTEMPTS:-12}"); do
for attempt in $(seq 1 "${APPROVAL_CHECK_WAIT_ATTEMPTS:-36}"); do
curl_api_read "${api_url}/repos/${GH_REPOSITORY}/commits/${HEAD_SHA}/check-runs?per_page=100" >"$check_runs_file"
jq -r "${self_check_filter}
latest_peer_checks
Expand All @@ -3767,8 +3770,8 @@ jobs:
if [ ! -s "$pending_checks_file" ]; then
break
fi
if [ "$attempt" -lt "${APPROVAL_CHECK_WAIT_ATTEMPTS:-12}" ]; then
printf 'Central fast approval waiting for peer checks (%s/%s):\n' "$attempt" "${APPROVAL_CHECK_WAIT_ATTEMPTS:-12}"
if [ "$attempt" -lt "${APPROVAL_CHECK_WAIT_ATTEMPTS:-36}" ]; then
printf 'Central fast approval waiting for peer checks (%s/%s):\n' "$attempt" "${APPROVAL_CHECK_WAIT_ATTEMPTS:-36}"
cat "$pending_checks_file"
sleep "${APPROVAL_CHECK_WAIT_SLEEP_SECONDS:-10}"
fi
Expand Down Expand Up @@ -3930,7 +3933,9 @@ jobs:
# keeps GitHub review publication bounded. Failed-check evidence is
# collected from logs/SARIF before this point; central review-process
# self-repair must not run a second model pass from the publish step.
timeout-minutes: 8
# The approval gate may spend up to six minutes waiting for peer checks;
# retain bounded time for API retries and review publication afterwards.
timeout-minutes: 10
env:
GH_TOKEN: ${{ steps.opencode_app_token.outputs.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }}
CHECK_LOOKUP_GH_TOKEN: ${{ github.token }}
Expand Down Expand Up @@ -3973,7 +3978,7 @@ jobs:
CENTRAL_REVIEW_PROCESS_FALLBACK_SCOPE_LABEL: ${{ steps.central_review_process_fallback_scope.outputs.scope_label || 'unsupported' }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.inputs.pr_base_sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.inputs.pr_head_sha }}
APPROVAL_CHECK_WAIT_ATTEMPTS: "12"
APPROVAL_CHECK_WAIT_ATTEMPTS: "36"
APPROVAL_CHECK_WAIT_SLEEP_SECONDS: "10"
CHECK_LOOKUP_RETRY_ATTEMPTS: "1"
CHECK_LOOKUP_RETRY_SLEEP_SECONDS: "2"
Expand Down Expand Up @@ -6011,7 +6016,7 @@ jobs:
| select((.workflow // "") != "Required OpenCode Review")
| select((.workflow // "") != "OpenCode PR Review")
| select((.conclusion // "" | ascii_upcase) as $c | ["FAILURE","TIMED_OUT","ACTION_REQUIRED","CANCELLED","STARTUP_FAILURE"] | index($c))
| select(((.name // "") == "metadata-only gate evaluation" and (.workflow // "") == "PR Governance") | not)
| select((.name // "") != "metadata-only gate evaluation")
| select(((.conclusion // "" | ascii_downcase) == "cancelled" and ((.isRequired // false) | not) and (.workflow // "") == "CodeQL") | not)
| select(((.conclusion // "" | ascii_downcase) == "cancelled" and (.name // "") == "scan-pr-queue" and ((.workflow // "") == "PR Review Merge Scheduler" or (.workflow // "") == "Required PR Review Merge Scheduler")) | not)
| select(((.conclusion // "" | ascii_downcase) == "cancelled" and ((.name // "") | contains("$" + "{{"))) | not)
Expand Down Expand Up @@ -6138,7 +6143,7 @@ jobs:
| select((.workflow // "") != "OpenCode Review")
| select((.workflow // "") != "Required OpenCode Review")
| select((.workflow // "") != "OpenCode PR Review")
| select(((.name // "") == "metadata-only gate evaluation" and (.workflow // "") == "PR Governance") | not)
| select((.name // "") != "metadata-only gate evaluation")
| select((.status // "") != "COMPLETED")
| "- " + (.label // "check") + ": " + (.status // "unknown") + (if (.detailsUrl // "") != "" then " (" + .detailsUrl + ")" else "" end)
elif .kind == "status" then
Expand Down Expand Up @@ -6247,8 +6252,8 @@ jobs:

wait_for_peer_github_checks() {
local output_file="$1"
local attempts="${APPROVAL_CHECK_WAIT_ATTEMPTS:-10}"
local sleep_seconds="${APPROVAL_CHECK_WAIT_SLEEP_SECONDS:-15}"
local attempts="${APPROVAL_CHECK_WAIT_ATTEMPTS:-36}"
local sleep_seconds="${APPROVAL_CHECK_WAIT_SLEEP_SECONDS:-10}"
local attempt=1

while [ "$attempt" -le "$attempts" ]; do
Expand Down
5 changes: 4 additions & 1 deletion scripts/ci/collect_failed_check_evidence.sh
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,10 @@ gh api graphql \
if .__typename == "CheckRun" then
select((.status // "") == "COMPLETED")
| select((.conclusion // "" | ascii_upcase) as $c | ["FAILURE","TIMED_OUT","ACTION_REQUIRED","CANCELLED","STARTUP_FAILURE"] | index($c))
| select(((.name // "") == "metadata-only gate evaluation" and (.checkSuite.workflowRun.workflow.name // "") == "PR Governance") | not)
# The metadata-only gate is derived from the OpenCode review state.
# GitHub can misattribute its workflow name (for example, as CodeQL),
# so its stable check name is the only safe cycle-breaking key.
| select((.name // "") != "metadata-only gate evaluation")
| select(((.conclusion // "" | ascii_downcase) == "cancelled" and ((.isRequired // false) | not) and (.checkSuite.workflowRun.workflow.name // "") == "CodeQL") | not)
| select(((.conclusion // "" | ascii_downcase) == "cancelled" and (.name // "") == "scan-pr-queue" and ((.checkSuite.workflowRun.workflow.name // "") == "PR Review Merge Scheduler" or (.checkSuite.workflowRun.workflow.name // "") == "Required PR Review Merge Scheduler")) | not)
| select(((.conclusion // "" | ascii_downcase) == "cancelled" and ((.name // "") | contains("${{"))) | not)
Expand Down
10 changes: 5 additions & 5 deletions scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -656,12 +656,12 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$workflow_file" 'load_selected_review_output()' "opencode approval step has a direct selected-output fallback when the overview comment is stale or invalid"
assert_file_contains "$workflow_file" "gate result from Review Overview comment" "opencode approval step distinguishes overview-comment gate results"
assert_file_contains "$workflow_file" "gate result from selected OpenCode output" "opencode approval step can recover from an invalid overview by validating the selected successful output"
assert_file_contains "$workflow_file" 'timeout-minutes: 8' "opencode approval step has a short bounded wall-clock timeout"
assert_file_contains "$workflow_file" 'timeout-minutes: 10' "opencode approval step has a bounded wall-clock timeout that covers slow peer checks"
assert_file_contains "$workflow_file" 'OPENCODE_RUN_TIMEOUT_SECONDS: "120"' "opencode publish-stage diagnosis is a short best-effort augmentation"
assert_file_not_contains "$workflow_file" "rekick_model_pool_on_exhaustion" "opencode publication must not rerun the exhausted model catalog after the model-pool step"
assert_file_contains "$workflow_file" "publish stage performs no duplicate model-catalog pass" "opencode publication logs that exhausted model retries are delegated to the scheduler"
assert_file_contains "$workflow_file" 'timeout --kill-after=15s "${OPENCODE_EXPORT_TIMEOUT_SECONDS:-120}s"' "opencode failed-check diagnosis bounds export so the publication gate cannot hang silently"
assert_file_contains "$workflow_file" 'APPROVAL_CHECK_WAIT_ATTEMPTS: "12"' "opencode approval gives slow peer checks a bounded hold window before scheduler retry"
assert_file_contains "$workflow_file" 'APPROVAL_CHECK_WAIT_ATTEMPTS: "36"' "opencode approval gives slow peer checks a bounded six-minute hold window before scheduler retry"
assert_file_contains "$workflow_file" 'APPROVAL_CHECK_WAIT_SLEEP_SECONDS: "10"' "opencode approval poll cadence keeps peer-check API volume bounded"
assert_file_not_contains "$workflow_file" 'REVIEW_PUBLISH_STEP_TIMEOUT_SECONDS' "opencode review publication relies on the Actions step timeout instead of a background watchdog"
assert_file_not_contains "$workflow_file" "PUBLISH_STEP_TIMEOUT" "opencode review publication does not leave orphaned watchdog processes"
Expand Down Expand Up @@ -898,14 +898,14 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" 'select((.workflowName // "") == "Strix Security Scan" or (.workflowName // "") == "Strix")' "failed-check evidence only appends Strix workflow runs"
assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" 'group_by(.__context_key)' "failed-check evidence groups manual Strix statuses by context before accepting superseding success"
assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" 'map(last)' "failed-check evidence accepts only the latest status per context"
assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" 'select(((.name // "") == "metadata-only gate evaluation" and (.checkSuite.workflowRun.workflow.name // "") == "PR Governance") | not)' "failed-check evidence ignores metadata-only PR Governance review-state gates"
assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" 'select((.name // "") != "metadata-only gate evaluation")' "failed-check evidence ignores metadata-only review-state gates even when GitHub misattributes their workflow"
assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" 'isRequired(pullRequestId: $prId)' "failed-check evidence reads PR-required status for check runs"
assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" '((.isRequired // false) | not) and (.checkSuite.workflowRun.workflow.name // "") == "CodeQL"' "failed-check evidence ignores non-required cancelled CodeQL checks without logs"
assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" '(.name // "") == "scan-pr-queue" and ((.checkSuite.workflowRun.workflow.name // "") == "PR Review Merge Scheduler" or (.checkSuite.workflowRun.workflow.name // "") == "Required PR Review Merge Scheduler")' "failed-check evidence ignores cancelled scheduler queue replacement checks"
assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" '((.name // "") | contains("${{"))' "failed-check evidence ignores cancelled matrix-template helper checks without logs"
assert_file_contains "$REPO_ROOT/scripts/ci/collect_failed_check_evidence.sh" '(.name // "") == "noema-review"' "failed-check evidence ignores cancelled Noema queue replacement checks without source logs"
assert_file_contains "$workflow_file" 'select(((.name // "") == "metadata-only gate evaluation" and (.workflow // "") == "PR Governance") | not)' "opencode failed-check collection ignores metadata-only PR Governance review-state gates"
metadata_gate_filter_count="$(grep -Fc 'select(((.name // "") == "metadata-only gate evaluation" and (.workflow // "") == "PR Governance") | not)' "$workflow_file")"
assert_file_contains "$workflow_file" 'select((.name // "") != "metadata-only gate evaluation")' "opencode failed-check collection ignores metadata-only review-state gates regardless of workflow attribution"
metadata_gate_filter_count="$(grep -Fc 'select((.name // "") != "metadata-only gate evaluation")' "$workflow_file")"
if [ "$metadata_gate_filter_count" -lt 2 ]; then
fail "opencode failed- and pending-check collection both ignore metadata-only PR Governance review-state gates (found ${metadata_gate_filter_count}, expected at least 2)"
fi
Expand Down
50 changes: 48 additions & 2 deletions tests/test_opencode_agent_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,8 +719,9 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent():
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)
assert re.search(r"Publish OpenCode review outcome[\s\S]{0,900}timeout-minutes: 8", workflow)
assert 'APPROVAL_CHECK_WAIT_ATTEMPTS: "12"' in workflow
assert re.search(r"Publish central OpenCode fast approval[\s\S]{0,900}timeout-minutes: 8", workflow)
assert re.search(r"Publish OpenCode review outcome[\s\S]{0,900}timeout-minutes: 10", workflow)
assert workflow.count('APPROVAL_CHECK_WAIT_ATTEMPTS: "36"') == 2
assert 'APPROVAL_CHECK_WAIT_SLEEP_SECONDS: "10"' in workflow
assert 'CHECK_LOOKUP_GH_API_TIMEOUT_SECONDS: "15"' in workflow
assert 'OPENCODE_RUN_TIMEOUT_SECONDS: "120"' in workflow
Expand Down Expand Up @@ -834,6 +835,17 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent():
assert re.search(r'check-runs" \\\n\s+-f per_page=100 \\\n\s+--paginate \\\n\s+--slurp \|\n\s+jq -r "\$jq_filter"', workflow)
assert not re.search(r"--slurp\s*\\\n\s*--jq", workflow)
assert workflow.count('["opencode-review","coverage-evidence","metadata-only gate evaluation"]') >= 2
metadata_gate_filter = 'select((.name // "") != "metadata-only gate evaluation")'
assert workflow.count(metadata_gate_filter) >= 2
failed_check_collector = Path(
"scripts/ci/collect_failed_check_evidence.sh"
).read_text(encoding="utf-8")
assert metadata_gate_filter in failed_check_collector
assert (
'(.name // "") == "metadata-only gate evaluation" and '
'(.checkSuite.workflowRun.workflow.name // "") == "PR Governance"'
not in failed_check_collector
)
assert "falling back to current-head REST check-runs" in workflow

strix_workflow = Path(".github/workflows/strix.yml").read_text(encoding="utf-8")
Expand Down Expand Up @@ -1287,3 +1299,37 @@ def test_opencode_review_thread_jq_filters_preserve_bash_single_quotes():

assert 'gsub("`"; "\'")' not in workflow
assert workflow.count('gsub("`"; "'")') == 4


def test_peer_check_wait_budget_fits_publication_step_timeouts():
"""Keep slow-check cadence bounded inside both publication step caps."""
workflow = Path(".github/workflows/opencode-review.yml").read_text(
encoding="utf-8"
)

attempts = [
int(value)
for value in re.findall(r'APPROVAL_CHECK_WAIT_ATTEMPTS: "(\d+)"', workflow)
]
sleeps = [
int(value)
for value in re.findall(
r'APPROVAL_CHECK_WAIT_SLEEP_SECONDS: "(\d+)"', workflow
)
]
fast_timeout = re.search(
r"Publish central OpenCode fast approval[\s\S]{0,900}timeout-minutes: (\d+)",
workflow,
)
publish_timeout = re.search(
r"Publish OpenCode review outcome[\s\S]{0,900}timeout-minutes: (\d+)",
workflow,
)

assert attempts == [36, 36]
assert sleeps == [10, 10]
assert fast_timeout is not None
assert publish_timeout is not None
wait_seconds = (attempts[0] - 1) * sleeps[0]
assert int(fast_timeout.group(1)) * 60 - wait_seconds >= 120
assert int(publish_timeout.group(1)) * 60 - wait_seconds >= 240
Loading