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
21 changes: 16 additions & 5 deletions .github/workflows/opencode-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand All @@ -105,11 +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"
auth_header="$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')"
if ! git -C "$fetch_dir" \
-c "http.${GITHUB_SERVER_URL}/.extraheader=AUTHORIZATION: basic ${auth_header}" \
-c http.extraheader="AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules origin "$PR_BASE_SHA" "$PR_HEAD_SHA"; then
echo "::error::Coverage fetch could not authenticate to ${TARGET_REPOSITORY} or read base/head SHAs ${PR_BASE_SHA}/${PR_HEAD_SHA}; check token permissions, target repository access, and SHA visibility."
exit 1
Expand Down Expand Up @@ -2879,6 +2884,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
Expand Down Expand Up @@ -4225,7 +4236,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)
Expand All @@ -4238,7 +4249,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)
'
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -88,6 +89,7 @@ jobs:
--format=json
--output=old-results.json
--no-resolve
--allow-no-lockfiles
-r
./
- name: Checkout head
Expand All @@ -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
Expand All @@ -121,6 +124,7 @@ jobs:
--format=json
--output=new-results.json
--no-resolve
--allow-no-lockfiles
-r
./
- name: Require OSV scan output
Expand Down
26 changes: 26 additions & 0 deletions scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -828,6 +839,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"
Expand Down
9 changes: 6 additions & 3 deletions tests/test_opencode_agent_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,12 @@ 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 "x-access-token:%s" in step
assert "AUTHORIZATION: basic ${auth_header}" in step
assert "AUTHORIZATION: bearer" not 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 "AUTHORIZATION: bearer" not in step
assert 'fetch --no-tags --prune --no-recurse-submodules origin "$PR_BASE_SHA" "$PR_HEAD_SHA"' in step
assert "Coverage fetch could not authenticate" in step
assert 'merge --no-ff --no-edit "$PR_HEAD_SHA"' in step
Expand Down Expand Up @@ -372,6 +374,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")
Expand Down
10 changes: 10 additions & 0 deletions tests/test_required_workflow_queue_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
Loading