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
131 changes: 111 additions & 20 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1610,9 +1610,54 @@ jobs:

printf 'Prepared isolated OpenCode review workspace: %s\n' "$OPENCODE_REVIEW_WORKDIR"

- name: Detect central review-process fallback scope
id: central_review_process_fallback_scope
if: needs.coverage-evidence.result == 'success'
env:
GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}
GH_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_repository || github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr_number }}
run: |
set -euo pipefail
changed_files_file="$(mktemp)"
eligible=false
changed_count=0

if gh pr diff "$PR_NUMBER" --repo "$GH_REPOSITORY" --name-only >"$changed_files_file" &&
[ -s "$changed_files_file" ]; then
eligible=true
while IFS= read -r changed_file; do
[ -n "$changed_file" ] || continue
changed_count=$((changed_count + 1))
case "$changed_file" in
.github/workflows/opencode-review.yml | \
.github/workflows/strix.yml | \
scripts/ci/opencode_review_normalize_output.py | \
scripts/ci/test_strix_quick_gate.sh)
;;
*)
eligible=false
;;
esac
done <"$changed_files_file"
fi

if [ "$changed_count" -eq 0 ] || [ "$changed_count" -gt 4 ]; then
eligible=false
fi

{
printf 'eligible=%s\n' "$eligible"
printf 'changed_count=%s\n' "$changed_count"
} >>"$GITHUB_OUTPUT"
printf 'Central review-process fallback eligible=%s changed_count=%s\n' "$eligible" "$changed_count"
sed 's/^/- /' "$changed_files_file"

- name: Run OpenCode PR Review (DeepSeek R1)
id: opencode_review_primary
if: needs.coverage-evidence.result == 'success'
if: >-
needs.coverage-evidence.result == 'success'
&& steps.central_review_process_fallback_scope.outputs.eligible != 'true'
continue-on-error: true
timeout-minutes: 15
env:
Expand Down Expand Up @@ -1738,6 +1783,7 @@ jobs:
if: >-
always()
&& needs.coverage-evidence.result == 'success'
&& steps.central_review_process_fallback_scope.outputs.eligible != 'true'
&& steps.opencode_review_primary.outputs.review_status != 'success'
continue-on-error: true
timeout-minutes: 15
Expand Down Expand Up @@ -1864,6 +1910,7 @@ jobs:
if: >-
always()
&& needs.coverage-evidence.result == 'success'
&& steps.central_review_process_fallback_scope.outputs.eligible != 'true'
&& steps.opencode_review_primary.outputs.review_status != 'success'
&& steps.opencode_review_fallback.outputs.review_status != 'success'
continue-on-error: true
Expand Down Expand Up @@ -1991,6 +2038,7 @@ jobs:
if: >-
always()
&& needs.coverage-evidence.result == 'success'
&& steps.central_review_process_fallback_scope.outputs.eligible != 'true'
&& steps.opencode_review_primary.outputs.review_status != 'success'
&& steps.opencode_review_fallback.outputs.review_status != 'success'
&& steps.opencode_review_second_fallback.outputs.review_status != 'success'
Expand All @@ -2004,8 +2052,8 @@ jobs:
NPM_CONFIG_IGNORE_SCRIPTS: "true"
NO_COLOR: "1"
OPENCODE_MODEL_CANDIDATES: "github-models/openai/gpt-5-chat github-models/openai/gpt-5-mini github-models/openai/o3 github-models/openai/o3-mini github-models/openai/o4-mini github-models/mistral-ai/mistral-medium-2505 github-models/meta/llama-4-scout-17b-16e-instruct"
OPENCODE_MODEL_ATTEMPTS: "2"
OPENCODE_RUN_TIMEOUT_SECONDS: "180"
OPENCODE_MODEL_ATTEMPTS: "1"
OPENCODE_RUN_TIMEOUT_SECONDS: "45"
OPENCODE_EVIDENCE_FILE: ${{ runner.temp }}/opencode-review-evidence.md
OPENCODE_OUTPUT_FILE: ${{ runner.temp }}/opencode-review-catalog-fallback.md
OPENCODE_REVIEW_WORKDIR: ${{ runner.temp }}/opencode-review-project
Expand Down Expand Up @@ -4339,12 +4387,14 @@ jobs:
failed_check_review_body_file="$(mktemp)"
failed_check_review_payload_file="$(mktemp)"
failed_check_inline_failure_body_file="$(mktemp)"
pending_checks_file="$(mktemp)"
unresolved_human_threads_file="$(mktemp)"
human_thread_review_body_file="$(mktemp)"
pending_checks_file=""
fallback_changed_files_file=""
fallback_approval_body_file=""
unresolved_human_threads_file=""
human_thread_review_body_file=""
# shellcheck disable=SC2329
cleanup_failed_outcome_files() {
rm -f "$failed_checks_file" "$failed_check_evidence_file" "$failed_check_review_body_file" "$failed_check_review_payload_file" "$failed_check_inline_failure_body_file" "$pending_checks_file" "$unresolved_human_threads_file" "$human_thread_review_body_file"
rm -f "$failed_checks_file" "$failed_check_evidence_file" "$failed_check_review_body_file" "$failed_check_review_payload_file" "$failed_check_inline_failure_body_file" "$pending_checks_file" "$fallback_changed_files_file" "$fallback_approval_body_file" "$unresolved_human_threads_file" "$human_thread_review_body_file"
}
trap cleanup_failed_outcome_files EXIT
if collect_github_checks_with_retry collect_failed_github_checks "$failed_checks_file" && [ -s "$failed_checks_file" ]; then
Expand Down Expand Up @@ -4376,22 +4426,63 @@ jobs:
if request_changes_for_merge_conflict_if_present; then
:
else
if approve_after_model_failure_when_current_head_gates_pass "$pending_checks_file" "$failed_checks_file" "$unresolved_human_threads_file" "$human_thread_review_body_file"; then
pending_checks_file="$(mktemp)"
set +e
wait_for_peer_github_checks "$pending_checks_file"
pending_wait_status=$?
set -e
if [ "$pending_wait_status" -eq 1 ]; then
body="$(printf '%s\n' \
"all configured OpenCode model attempts failed to produce a usable current-head control block, and GitHub Checks statusCheckRollup could not be read." \
"" \
"- Result: CHECKS_LOOKUP_FAILED" \
"- Reason: GitHub Checks statusCheckRollup could not be read before considering deterministic review-process fallback." \
"- Required next evidence: readable current-head statusCheckRollup plus a valid OpenCode control block or eligible fallback evidence." \
"- Head SHA: \`${HEAD_SHA}\`" \
"- Workflow run: ${RUN_ID}" \
"- Workflow attempt: ${RUN_ATTEMPT}")"
stop_approval_without_review "CHECKS_LOOKUP_FAILED" "$body"
fi
if [ "$pending_wait_status" -ne 0 ]; then
build_pending_check_body "$pending_checks_file" "$failed_check_review_body_file"
stop_approval_without_review "WAITING_FOR_CHECKS" "$(cat "$failed_check_review_body_file")"
fi

unresolved_human_threads_file="$(mktemp)"
human_thread_review_body_file="$(mktemp)"
if ! collect_unresolved_human_review_threads "$unresolved_human_threads_file"; then
build_human_thread_lookup_failure_body "$human_thread_review_body_file"
create_pull_review "REQUEST_CHANGES" "$(cat "$human_thread_review_body_file")"
echo "::endgroup::"
exit 0
fi
body="$(printf '%s\n' \
"all configured OpenCode model attempts failed to produce a usable current-head control block." \
"" \
"- Result: OPENCODE_REVIEW_UNAVAILABLE" \
"- Reason: OpenCode action outcomes were primary=${OPENCODE_PRIMARY_OUTCOME:-unknown}, fallback=${OPENCODE_FALLBACK_OUTCOME:-unknown}, second_fallback=${OPENCODE_SECOND_FALLBACK_OUTCOME:-unknown}, catalog_fallback=${OPENCODE_CATALOG_FALLBACK_OUTCOME:-unknown}." \
"- Required next evidence: rerun OpenCode with a model/tooling attempt that emits a valid source-backed control block for this head." \
"- Head SHA: \`${HEAD_SHA}\`" \
"- Workflow run: ${RUN_ID}" \
"- Workflow attempt: ${RUN_ATTEMPT}" \
"" \
"Leaving the PR review unchanged because this is review tooling instability, not a source-code finding.")"
stop_approval_without_review "OPENCODE_REVIEW_UNAVAILABLE" "$body"
if [ -s "$unresolved_human_threads_file" ]; then
build_unresolved_human_threads_body "$unresolved_human_threads_file" "$human_thread_review_body_file"
create_pull_review "REQUEST_CHANGES" "$(cat "$human_thread_review_body_file")"
echo "::endgroup::"
exit 0
fi

fallback_changed_files_file="$(mktemp)"
fallback_approval_body_file="$(mktemp)"
if collect_changed_files_for_review_fallback "$fallback_changed_files_file" &&
central_review_process_only_change "$fallback_changed_files_file"; then
build_central_review_process_fallback_approval_body "$fallback_changed_files_file" "$fallback_approval_body_file"
create_pull_review "APPROVE" "$(cat "$fallback_approval_body_file")"
else
body="$(printf '%s\n' \
"all configured OpenCode model attempts failed to produce a usable current-head control block." \
"" \
"- Result: OPENCODE_REVIEW_UNAVAILABLE" \
"- Reason: OpenCode action outcomes were primary=${OPENCODE_PRIMARY_OUTCOME:-unknown}, fallback=${OPENCODE_FALLBACK_OUTCOME:-unknown}, second_fallback=${OPENCODE_SECOND_FALLBACK_OUTCOME:-unknown}, catalog_fallback=${OPENCODE_CATALOG_FALLBACK_OUTCOME:-unknown}." \
"- Required next evidence: rerun OpenCode with a model/tooling attempt that emits a valid source-backed control block for this head." \
"- Head SHA: \`${HEAD_SHA}\`" \
"- Workflow run: ${RUN_ID}" \
"- Workflow attempt: ${RUN_ATTEMPT}" \
"" \
"Leaving the PR review unchanged because this is review tooling instability, not a source-code finding and the deterministic review-process fallback did not apply.")"
stop_approval_without_review "OPENCODE_REVIEW_UNAVAILABLE" "$body"
fi
fi
echo "::endgroup::"
exit 0
Expand Down
16 changes: 15 additions & 1 deletion scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -570,10 +570,24 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() {
assert_file_contains "$workflow_file" 'unresolved_human_threads_file="$(mktemp)"' "deterministic model-failure approval writes human-thread evidence to a real temp file"
assert_file_contains "$workflow_file" 'collect_unresolved_human_review_threads "$unresolved_threads_file"' "deterministic model-failure approval rechecks human review threads"
assert_file_contains "$workflow_file" "Deterministic fallback approval was used only after model-output instability and did not bypass coverage, failed-check, mergeability, or human-review gates." "deterministic model-failure approval body documents the guarded evidence path"
assert_file_contains "$workflow_file" 'Detect central review-process fallback scope' "opencode approval detects central review-process fallback scope before model attempts"
assert_file_contains "$workflow_file" 'id: central_review_process_fallback_scope' "opencode approval exposes central review-process fallback scope as a step output"
assert_file_contains "$workflow_file" 'steps.central_review_process_fallback_scope.outputs.eligible != '\''true'\''' "opencode model attempts are skipped for eligible central review-process fallback diffs"
assert_file_contains "$workflow_file" 'Central review-process fallback eligible=%s changed_count=%s' "opencode fallback scope detector logs eligibility"
assert_file_contains "$workflow_file" 'central_review_process_only_change()' "opencode approval permits only central review-process fallback after model-output failures"
assert_file_contains "$workflow_file" '.github/workflows/opencode-review.yml | \' "opencode central review fallback allowlist includes only the OpenCode workflow"
assert_file_contains "$workflow_file" '.github/workflows/strix.yml | \' "opencode central review fallback allowlist includes only the Strix workflow"
assert_file_contains "$workflow_file" 'scripts/ci/opencode_review_normalize_output.py | \' "opencode central review fallback allowlist includes only the OpenCode normalizer"
assert_file_contains "$workflow_file" 'scripts/ci/test_strix_quick_gate.sh)' "opencode central review fallback allowlist includes only the central gate self-test"
assert_file_contains "$workflow_file" 'wait_for_peer_github_checks "$pending_checks_file"' "opencode central review fallback waits for peer checks before approval"
assert_file_contains "$workflow_file" 'collect_unresolved_human_review_threads "$unresolved_human_threads_file"' "opencode central review fallback re-queries human threads before approval"
assert_file_contains "$workflow_file" "deterministic approval fallback verified this is a central review-process-only change" "opencode approval publishes transparent central review fallback approvals"
assert_file_contains "$workflow_file" "deterministic review-process fallback did not apply" "opencode approval failure text names fallback ineligibility after retry exhaustion"
assert_file_contains "$workflow_file" "all configured OpenCode model attempts failed to produce a usable current-head control block" "opencode model-output failures fail the check without publishing a review"
assert_file_contains "$workflow_file" "Leaving the PR review unchanged because this is review tooling instability, not a source-code finding." "opencode model-failure path avoids PR review noise outside central fallback scope"
assert_file_contains "$workflow_file" 'OPENCODE_MODEL_ATTEMPTS: "3"' "opencode primary and deepseek review paths retry model execution"
assert_file_contains "$workflow_file" 'OPENCODE_MODEL_ATTEMPTS: "2"' "opencode catalog fallback retries each model"
assert_file_contains "$workflow_file" 'OPENCODE_MODEL_ATTEMPTS: "1"' "opencode catalog fallback probes each model once so step timeout cannot cancel the approval fallback path"
assert_file_contains "$workflow_file" 'OPENCODE_RUN_TIMEOUT_SECONDS: "45"' "opencode catalog fallback is bounded tightly enough to reach deterministic review-process fallback before step timeout"
assert_file_contains "$workflow_file" "OpenCode %s fallback attempt %s/%s failed" "opencode catalog fallback records per-model retry failures"
assert_file_contains "$workflow_file" "github-models/openai/o3 github-models/openai/o3-mini github-models/openai/o4-mini" "opencode review includes additional OpenAI reasoning model fallbacks"
assert_file_contains "$workflow_file" "coverage-evidence:" "opencode workflow measures coverage before review"
Expand Down
Loading