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
36 changes: 26 additions & 10 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3660,10 +3660,9 @@ jobs:
&& steps.opencode_review_model_pool.outputs.review_status == 'success'
&& steps.central_review_process_fallback_scope.outputs.eligible == 'true'
continue-on-error: true
# 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
# Keep the normal peer-check hold short, but leave bounded room for the
# dynamic image-validation extension and review publication overhead.
timeout-minutes: 14
env:
GH_TOKEN: ${{ steps.opencode_app_token.outputs.token }}
CHECK_LOOKUP_GH_TOKEN: ${{ github.token }}
Expand All @@ -3683,6 +3682,7 @@ jobs:
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: "36"
APPROVAL_SLOW_IMAGE_CHECK_WAIT_ATTEMPTS: "60"
APPROVAL_CHECK_WAIT_SLEEP_SECONDS: "10"
REVIEW_PUBLISH_GH_API_TIMEOUT_SECONDS: "15"
run: |
Expand Down Expand Up @@ -3770,7 +3770,10 @@ jobs:
check_runs_file="$(mktemp)"
pending_checks_file="$(mktemp)"
failed_checks_file="$(mktemp)"
for attempt in $(seq 1 "${APPROVAL_CHECK_WAIT_ATTEMPTS:-36}"); do
attempts="${APPROVAL_CHECK_WAIT_ATTEMPTS:-36}"
slow_image_attempts="${APPROVAL_SLOW_IMAGE_CHECK_WAIT_ATTEMPTS:-60}"
attempt=1
while [ "$attempt" -le "$attempts" ]; 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 @@ -3780,11 +3783,17 @@ jobs:
if [ ! -s "$pending_checks_file" ]; then
break
fi
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}"
if [ "$attempts" -lt "$slow_image_attempts" ] &&
grep -Eiq -- '^- validate [^:/]+ image:' "$pending_checks_file"; then
printf '::notice::Extending central fast approval peer-check wait from %s to %s attempts because current-head image validation is still running.\n' "$attempts" "$slow_image_attempts"
attempts="$slow_image_attempts"
fi
if [ "$attempt" -lt "$attempts" ]; then
printf 'Central fast approval waiting for peer checks (%s/%s):\n' "$attempt" "$attempts"
cat "$pending_checks_file"
sleep "${APPROVAL_CHECK_WAIT_SLEEP_SECONDS:-10}"
fi
attempt=$((attempt + 1))
done
if [ -s "$pending_checks_file" ]; then
echo "::error::CENTRAL_FAST_APPROVAL_WAITING_FOR_CHECKS: peer GitHub Checks remained pending for current head ${HEAD_SHA}."
Expand Down Expand Up @@ -3943,9 +3952,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.
# 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
# The approval gate normally waits about six minutes, with a bounded
# extension for image validation plus API and publication overhead.
timeout-minutes: 16
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 @@ -3989,6 +3998,7 @@ jobs:
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: "36"
APPROVAL_SLOW_IMAGE_CHECK_WAIT_ATTEMPTS: "60"
APPROVAL_CHECK_WAIT_SLEEP_SECONDS: "10"
CHECK_LOOKUP_RETRY_ATTEMPTS: "1"
CHECK_LOOKUP_RETRY_SLEEP_SECONDS: "2"
Expand Down Expand Up @@ -6263,6 +6273,7 @@ jobs:
wait_for_peer_github_checks() {
local output_file="$1"
local attempts="${APPROVAL_CHECK_WAIT_ATTEMPTS:-36}"
local slow_image_attempts="${APPROVAL_SLOW_IMAGE_CHECK_WAIT_ATTEMPTS:-60}"
local sleep_seconds="${APPROVAL_CHECK_WAIT_SLEEP_SECONDS:-10}"
local attempt=1

Expand All @@ -6273,6 +6284,11 @@ jobs:
if [ ! -s "$output_file" ]; then
return 0
fi
if [ "$attempts" -lt "$slow_image_attempts" ] &&
grep -Eiq -- '^- (Build and Publish Docker Images/)?validate [^:/]+ image:' "$output_file"; then
printf '::notice::Extending OpenCode peer-check wait from %s to %s attempts because current-head image validation is still running.\n' "$attempts" "$slow_image_attempts"
attempts="$slow_image_attempts"
fi
if [ "$attempt" -lt "$attempts" ]; then
printf 'Waiting for peer GitHub Checks before OpenCode approval (%s/%s):\n' "$attempt" "$attempts"
cat "$output_file"
Expand Down
2 changes: 1 addition & 1 deletion docs/org-required-workflow-rollout.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ The central `.github/workflows/opencode-review.yml` is now part of the active or
- Write posture: OpenCode may create review/comment side effects through the OpenCode app token when available; `github.token` remains the last fallback and publication failures are soft-failed
- Coverage execution posture: privileged `pull_request_target` coverage runs only for same-repository PR heads; fork PR heads must be covered by an unprivileged PR-side check or manually trusted dispatch before approval
- Fork posture: PR heads are fetched through `refs/pull/<number>/head` when direct head-SHA fetch is not available, so review can inspect fork PR source as data without executing it in the trusted workflow context
- Runtime posture: pre-model failed-check evidence waits are capped at about five minutes; the later approval gate still rechecks current-head peer checks before approving
- Runtime posture: pre-model failed-check evidence waits are capped at about five minutes; the later approval gate rechecks current-head peer checks and extends its bounded wait only while image-validation checks remain pending, logging the reason before approval

Keep the OpenCode required workflow active only while the central workflow keeps proving current-head coverage, CodeGraph initialization, bounded evidence, model review output, and approval-gate publication on the current head.

Expand Down
6 changes: 4 additions & 2 deletions scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$workflow_file" 'timeout-minutes: 90' "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: 65' "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"
assert_file_contains "$workflow_file" 'timeout-minutes: 14' "opencode fast approval publication is bounded around the dynamic image-check wait"
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: "600"' "opencode primary review advances after a bounded stalled provider attempt"
assert_file_contains "$workflow_file" 'OPENCODE_TOTAL_RETRY_BUDGET_SECONDS: "3600"' "opencode model pool exits before the step timeout so the approval gate can publish a reason"
Expand Down Expand Up @@ -658,13 +658,15 @@ 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: 10' "opencode approval step has a bounded wall-clock timeout that covers slow peer checks"
assert_file_contains "$workflow_file" 'timeout-minutes: 16' "opencode approval step has a bounded wall-clock timeout that covers dynamically extended image 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: "36"' "opencode approval gives slow peer checks a bounded six-minute hold window before scheduler retry"
assert_file_contains "$workflow_file" 'APPROVAL_SLOW_IMAGE_CHECK_WAIT_ATTEMPTS: "60"' "opencode approval dynamically extends its bounded hold only for current-head image validation"
assert_file_contains "$workflow_file" 'APPROVAL_CHECK_WAIT_SLEEP_SECONDS: "10"' "opencode approval poll cadence keeps peer-check API volume bounded"
assert_file_contains "$workflow_file" "current-head image validation is still running" "opencode approval logs why the peer-check wait budget was dynamically extended"
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"
assert_file_not_contains "$workflow_file" "OPENCODE_PUBLISH_TIMEOUT_WRAPPED" "opencode review publication does not re-exec the runner shell script"
Expand Down
58 changes: 53 additions & 5 deletions tests/test_opencode_agent_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,10 +727,12 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent():
assert "OpenCode model pool exceeded the outer" in workflow
assert 'OPENCODE_POOL_MAX_CYCLES: "0"' 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 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 re.search(r"Publish central OpenCode fast approval[\s\S]{0,900}timeout-minutes: 14", workflow)
assert re.search(r"Publish OpenCode review outcome[\s\S]{0,900}timeout-minutes: 16", workflow)
assert workflow.count('APPROVAL_CHECK_WAIT_ATTEMPTS: "36"') == 2
assert workflow.count('APPROVAL_SLOW_IMAGE_CHECK_WAIT_ATTEMPTS: "60"') == 2
assert 'APPROVAL_CHECK_WAIT_SLEEP_SECONDS: "10"' in workflow
assert workflow.count("current-head image validation is still running") == 2
assert 'CHECK_LOOKUP_GH_API_TIMEOUT_SECONDS: "15"' in workflow
assert 'OPENCODE_RUN_TIMEOUT_SECONDS: "120"' in workflow
assert "Skipping publish-step failed-check OpenCode diagnosis for central review-process self-repair" in workflow
Expand Down Expand Up @@ -1375,10 +1377,16 @@ def test_peer_check_wait_budget_fits_publication_step_timeouts():
encoding="utf-8"
)

attempts = [
normal_attempts = [
int(value)
for value in re.findall(r'APPROVAL_CHECK_WAIT_ATTEMPTS: "(\d+)"', workflow)
]
slow_image_attempts = [
int(value)
for value in re.findall(
r'APPROVAL_SLOW_IMAGE_CHECK_WAIT_ATTEMPTS: "(\d+)"', workflow
)
]
sleeps = [
int(value)
for value in re.findall(
Expand All @@ -1394,10 +1402,50 @@ def test_peer_check_wait_budget_fits_publication_step_timeouts():
workflow,
)

assert attempts == [36, 36]
assert normal_attempts == [36, 36]
assert slow_image_attempts == [60, 60]
assert sleeps == [10, 10]
assert fast_timeout is not None
assert publish_timeout is not None
wait_seconds = (attempts[0] - 1) * sleeps[0]
wait_seconds = (slow_image_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


def test_slow_peer_wait_matches_only_image_validation_checks():
"""Reject lookalike labels when selecting the extended peer-check budget."""
workflow = Path(".github/workflows/opencode-review.yml").read_text(
encoding="utf-8"
)
fast_pattern = r"^- validate [^:/]+ image:"
general_pattern = r"^- (Build and Publish Docker Images/)?validate [^:/]+ image:"

assert workflow.count(f"grep -Eiq -- '{fast_pattern}'") == 1
assert workflow.count(f"grep -Eiq -- '{general_pattern}'") == 1

probes = (
("- validate naruon image: in_progress\n", True, True),
(
"- Build and Publish Docker Images/validate frontend image: IN_PROGRESS\n",
False,
True,
),
("- invalidate naruon image: in_progress\n", False, False),
("- validate security/image: in_progress\n", False, False),
("- docs image validation: in_progress\n", False, False),
)
for candidate, fast_expected, general_expected in probes:
fast_match = subprocess.run(
["grep", "-Eiq", "--", fast_pattern],
input=candidate,
text=True,
check=False,
).returncode == 0
general_match = subprocess.run(
["grep", "-Eiq", "--", general_pattern],
input=candidate,
text=True,
check=False,
).returncode == 0
assert fast_match is fast_expected, candidate
assert general_match is general_expected, candidate
Loading