From f0efc7fad94f90c3aedc7582a206ec8b3b3dae61 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 10 Jul 2026 07:58:31 +0900 Subject: [PATCH 1/5] fix opencode approval rate-limit helper scope --- .github/workflows/opencode-review.yml | 6 ++++++ scripts/ci/test_strix_quick_gate.sh | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index b0f387961..e7bfd8184 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -2808,6 +2808,12 @@ jobs: fi } + gh_error_is_rate_limited() { + local error_file="$1" + [ -s "$error_file" ] || return 1 + grep -Eiq '(API rate limit exceeded|rate limit exceeded|secondary rate limit)' "$error_file" + } + emit_change_flow_mermaid_graph() { local merge_state="${1:-UNKNOWN}" local changed_files_file surfaces_file idx next_node diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index f51cfff59..5d0d4fa65 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -812,6 +812,21 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" 'warn_gh_publication_failure "pull review with fallback review token"' "opencode approval explains fallback review publication failures" assert_file_contains "$workflow_file" 'gh_error_is_rate_limited()' "opencode approval detects rate-limited publication failures" assert_file_contains "$workflow_file" '[ "$event" = "APPROVE" ] && gh_error_is_rate_limited "$gh_error_file"' "opencode approval only soft-fails rate-limited approve publication failures" + local approval_step + local helper_line + local approve_call_line + approval_step="$( + awk ' + /^[[:space:]]*- name: Approve PR if OpenCode review passed/ { in_step = 1 } + in_step { print } + in_step && /^[[:space:]]*- name: Run merge scheduler after approval/ { exit } + ' "$workflow_file" + )" + helper_line="$(grep -nF 'gh_error_is_rate_limited()' <<<"$approval_step" | head -n 1 | cut -d: -f1 || true)" + approve_call_line="$(grep -nF '[ "$event" = "APPROVE" ] && gh_error_is_rate_limited "$gh_error_file"' <<<"$approval_step" | head -n 1 | cut -d: -f1 || true)" + if [ -z "$helper_line" ] || [ -z "$approve_call_line" ] || [ "$helper_line" -ge "$approve_call_line" ]; then + record_failure "opencode approval step must define gh_error_is_rate_limited before create_pull_review uses it" + fi assert_file_contains "$workflow_file" 'OpenCode could not publish the APPROVE pull review for head %s because the GitHub API rate limit was exceeded' "opencode approval keeps successful gate results for rate-limited approval review publication" assert_file_contains "$workflow_file" 'OpenCode could not publish the pull review for head %s, so the review state was not changed.' "opencode approval fails when review publication fails" assert_file_contains "$workflow_file" 'warn_gh_publication_failure "review overview comment"' "opencode approval soft-fails permission-denied overview publication" From f2b1c7450a8a9ef6653bbaf62c6d937630bbb3ba Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 10 Jul 2026 08:58:34 +0900 Subject: [PATCH 2/5] fix(opencode): unblock coverage and OSV bootstrap gates --- .github/workflows/opencode-review.yml | 2 +- .github/workflows/security-scan.yml | 4 ++++ tests/test_opencode_agent_contract.py | 2 ++ tests/test_opencode_workflow_shell_syntax.py | 1 + tests/test_required_workflow_queue_contract.py | 10 ++++++++++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 088a19b81..338197f9a 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -108,7 +108,7 @@ jobs: git init "$fetch_dir" git -C "$fetch_dir" remote add origin "${GITHUB_SERVER_URL}/${TARGET_REPOSITORY}.git" git -C "$fetch_dir" \ - -c http."${GITHUB_SERVER_URL}/".extraheader="AUTHORIZATION: bearer ${GH_TOKEN}" \ + -c http.extraheader="AUTHORIZATION: bearer ${GH_TOKEN}" \ fetch --no-tags --prune --no-recurse-submodules origin "$PR_BASE_SHA" "$PR_HEAD_SHA" git -C "$fetch_dir" checkout --detach "$PR_BASE_SHA" git -C "$fetch_dir" config user.name "github-actions[bot]" diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index c2a1948c4..ce2dcc811 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -74,6 +74,7 @@ jobs: --format=json --output=old-results.json --maven-registry=https://maven-central.storage-download.googleapis.com/maven2 + --allow-no-lockfiles -r ./ - name: Explain base OSV resolver fallback @@ -88,6 +89,7 @@ jobs: --format=json --output=old-results.json --no-resolve + --allow-no-lockfiles -r ./ - name: Checkout head @@ -107,6 +109,7 @@ jobs: --format=json --output=new-results.json --maven-registry=https://maven-central.storage-download.googleapis.com/maven2 + --allow-no-lockfiles -r ./ - name: Explain head OSV resolver fallback @@ -121,6 +124,7 @@ jobs: --format=json --output=new-results.json --no-resolve + --allow-no-lockfiles -r ./ - name: Require OSV scan output diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 1b908e774..f05712550 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -158,6 +158,8 @@ def test_opencode_target_coverage_materializes_merge_tree_without_checkout_actio assert "uses: actions/checkout" not in step assert "refs/pull/${{ github.event.pull_request.number }}/merge" not in step assert "TARGET_REPOSITORY:" in step + assert '-c http.extraheader="AUTHORIZATION: bearer ${GH_TOKEN}"' in step + assert 'http."${GITHUB_SERVER_URL}/".extraheader' not in step assert 'fetch --no-tags --prune --no-recurse-submodules origin "$PR_BASE_SHA" "$PR_HEAD_SHA"' in step assert 'merge --no-ff --no-edit "$PR_HEAD_SHA"' in step assert 'Coverage merge tree could not be materialized' in step diff --git a/tests/test_opencode_workflow_shell_syntax.py b/tests/test_opencode_workflow_shell_syntax.py index 30485823b..15f651362 100644 --- a/tests/test_opencode_workflow_shell_syntax.py +++ b/tests/test_opencode_workflow_shell_syntax.py @@ -40,6 +40,7 @@ def test_opencode_review_run_blocks_are_valid_bash(): return for step_name in ( + "Materialize pull request merge tree for coverage measurement", "Prepare bounded OpenCode review evidence", "Publish OpenCode review outcome", ): diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 71495cde0..e132e25bc 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -98,6 +98,16 @@ def test_security_scan_skips_dependency_review_when_dependency_graph_is_unavaila assert "steps.dependency_review_support.outputs.supported == 'true'" in workflow +def test_security_scan_allows_repositories_without_supported_lockfiles() -> None: + workflow = workflow_text("security-scan.yml") + + assert workflow.count("--allow-no-lockfiles") == 4 + assert "--output=old-results.json" in workflow + assert "--output=new-results.json" in workflow + assert "test -s old-results.json" in workflow + assert "test -s new-results.json" in workflow + + def test_osv_scan_logs_and_retries_without_transitive_resolution_on_resolver_failure() -> None: workflow = workflow_text("security-scan.yml") From 26844683a7f2d4f3c37a0d8c153733898c1be833 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 10 Jul 2026 09:01:45 +0900 Subject: [PATCH 3/5] fix(opencode): use basic auth for coverage git fetch --- .github/workflows/opencode-review.yml | 8 +++++++- tests/test_opencode_agent_contract.py | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 338197f9a..092aeee85 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -105,10 +105,16 @@ jobs: set -euo pipefail fetch_dir="${RUNNER_TEMP}/opencode-coverage-fetch" rm -rf "$fetch_dir" "$COVERAGE_SOURCE_WORKDIR" + if [ -z "${GH_TOKEN:-}" ]; then + echo "::error::Coverage merge tree materialization requires a GitHub token." + exit 1 + fi + auth_header="$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')" + echo "::add-mask::$auth_header" git init "$fetch_dir" git -C "$fetch_dir" remote add origin "${GITHUB_SERVER_URL}/${TARGET_REPOSITORY}.git" git -C "$fetch_dir" \ - -c http.extraheader="AUTHORIZATION: bearer ${GH_TOKEN}" \ + -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ fetch --no-tags --prune --no-recurse-submodules origin "$PR_BASE_SHA" "$PR_HEAD_SHA" git -C "$fetch_dir" checkout --detach "$PR_BASE_SHA" git -C "$fetch_dir" config user.name "github-actions[bot]" diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index f05712550..af15e01aa 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -158,8 +158,11 @@ def test_opencode_target_coverage_materializes_merge_tree_without_checkout_actio assert "uses: actions/checkout" not in step assert "refs/pull/${{ github.event.pull_request.number }}/merge" not in step assert "TARGET_REPOSITORY:" in step - assert '-c http.extraheader="AUTHORIZATION: bearer ${GH_TOKEN}"' in step + assert 'printf \'x-access-token:%s\' "$GH_TOKEN" | base64 | tr -d \'\\n\'' in step + assert "echo \"::add-mask::$auth_header\"" in step + assert '-c http.extraheader="AUTHORIZATION: basic ${auth_header}"' in step assert 'http."${GITHUB_SERVER_URL}/".extraheader' not in step + assert 'AUTHORIZATION: bearer ${GH_TOKEN}' not in step assert 'fetch --no-tags --prune --no-recurse-submodules origin "$PR_BASE_SHA" "$PR_HEAD_SHA"' in step assert 'merge --no-ff --no-edit "$PR_HEAD_SHA"' in step assert 'Coverage merge tree could not be materialized' in step From caaf7e1068ce284a53704148fe625389ac313de9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 10 Jul 2026 09:10:49 +0900 Subject: [PATCH 4/5] fix opencode coverage target fetch token --- .github/workflows/opencode-review.yml | 2 +- scripts/ci/test_strix_quick_gate.sh | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 092aeee85..c2dc805b0 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -96,7 +96,7 @@ jobs: - name: Materialize pull request merge tree for coverage measurement env: - GH_TOKEN: ${{ github.event_name == 'workflow_dispatch' && (secrets.OPENCODE_APPROVE_TOKEN || github.token) || github.token }} + GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }} TARGET_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_repository || github.repository }} 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 }} diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index e738ad3b8..3aa14d57c 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -650,6 +650,17 @@ assert_opencode_review_uses_codegraph_and_gpt5_fallback() { assert_file_contains "$workflow_file" "coverage-evidence:" "opencode workflow measures coverage before review" assert_file_contains "$workflow_file" "Materialize pull request merge tree for coverage measurement" "required OpenCode reviews measure coverage instead of approving skipped coverage evidence" assert_file_not_contains "$workflow_file" "Exchange OpenCode app token for target repository coverage reads" "coverage evidence must not expose OIDC to PR-head test execution" + local coverage_merge_tree_step + coverage_merge_tree_step="$( + awk ' + /^[[:space:]]*- name: Materialize pull request merge tree for coverage measurement/ { in_step = 1 } + in_step { print } + in_step && /^[[:space:]]*- name:/ && $0 !~ /Materialize pull request merge tree for coverage measurement/ { exit } + ' "$workflow_file" + )" + if [[ "$coverage_merge_tree_step" != *'GH_TOKEN: ${{ secrets.OPENCODE_APPROVE_TOKEN || github.token }}'* ]]; then + record_failure "opencode coverage merge-tree fetch must use OPENCODE_APPROVE_TOKEN before github.token for target repository reads" + fi assert_file_contains "$workflow_file" 'fetch --no-tags --prune --no-recurse-submodules origin "$PR_BASE_SHA" "$PR_HEAD_SHA"' "coverage evidence fetches exact base and head commits as data" assert_file_contains "$workflow_file" 'merge --no-ff --no-edit "$PR_HEAD_SHA"' "coverage evidence materializes the current pull request merge tree without action checkout" assert_file_contains "$workflow_file" "Coverage merge tree could not be materialized" "coverage evidence logs an actionable merge-tree failure reason" From bf93542992f8ad8b6a756ff24a14cb76c87d059f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 10 Jul 2026 09:16:22 +0900 Subject: [PATCH 5/5] fix(opencode): ignore self coverage check fallback --- .github/workflows/opencode-review.yml | 4 ++-- tests/test_opencode_agent_contract.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index c2dc805b0..9e962705c 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -4233,7 +4233,7 @@ jobs: | group_by(.name // "") | map(last) | .[]? - | select((.name // "") != "opencode-review") + | select(((.name // "" | ascii_downcase) as $n | ["opencode-review","coverage-evidence"] | index($n)) | not) | select((.status // "") == "completed") | select((.conclusion // "" | ascii_upcase) as $c | ["FAILURE","TIMED_OUT","ACTION_REQUIRED","CANCELLED","STARTUP_FAILURE"] | index($c)) | "- " + (if (.name // "") == "strix" then "Strix Security Scan/strix" else ((.name // "check") + " check run") end) + ": " + (.conclusion // "unknown") + (if (.details_url // .html_url // "") != "" then " (" + (.details_url // .html_url) + ")" else "" end) @@ -4246,7 +4246,7 @@ jobs: | group_by(.name // "") | map(last) | .[]? - | select((.name // "") != "opencode-review") + | select(((.name // "" | ascii_downcase) as $n | ["opencode-review","coverage-evidence"] | index($n)) | not) | select((.status // "") != "completed") | "- " + (if (.name // "") == "strix" then "Strix Security Scan/strix" else ((.name // "check") + " check run") end) + ": " + (.status // "unknown") + (if (.details_url // .html_url // "") != "" then " (" + (.details_url // .html_url) + ")" else "" end) ' diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index af15e01aa..51b7af76d 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -372,6 +372,7 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert "${{ runner.temp }}/opencode-review-model-pool.md" in workflow 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"]') >= 2 assert "falling back to current-head REST check-runs" in workflow strix_workflow = Path(".github/workflows/strix.yml").read_text(encoding="utf-8")