From 6946f153194bd1cbfd9d8de2b4c810ca45df7182 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 13 Jul 2026 13:49:35 +0900 Subject: [PATCH] fix(review): stop preferring the OpenCode app token for code-scanning alert reads The model-unavailable evidence fallback in the "Publish OpenCode review outcome" step read open code-scanning alerts with CODE_SCANNING_GH_TOKEN preferring the OpenCode app installation token. That token is exchanged from api.opencode.ai and never carries security-events read, so GET /repos/{repo}/code-scanning/alerts fails with "Resource not accessible by integration" (HTTP 403) whenever the PAT secrets are absent, and an eligible fallback candidate dies with MODEL_OUTPUT_UNAVAILABLE during a provider outage (run 29223952170, job 86734424447, .github PR #508). The job token already grants security-events: read, and the central fast-approval step already reads alerts with github.token. Drop the app token from the code-scanning chain so same-repository runs fall back to github.token; PATs stay first for cross-repository dispatch. Contract test now pins the fixed chain and rejects the app token on both the token and token-source lines. Co-Authored-By: Claude Fable 5 --- .github/workflows/opencode-review.yml | 8 ++++++-- tests/test_opencode_agent_contract.py | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index e88cd0bfa..3169fd579 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -3916,9 +3916,13 @@ jobs: 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 }} - CODE_SCANNING_GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }} + # The OpenCode app installation token is exchanged from api.opencode.ai + # and never carries security-events read, so it cannot read the + # code-scanning alerts API; github.token has security-events: read from + # this job's permissions block, so it is the same-repository fallback. + CODE_SCANNING_GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || github.token }} CONFIGURED_REVIEW_WRITE_TOKEN_SOURCE: ${{ steps.opencode_app_token.outputs.available == 'true' && 'opencode-app' || secrets.PR_REVIEW_MERGE_TOKEN != '' && 'PR_REVIEW_MERGE_TOKEN' || secrets.OPENCODE_APPROVE_TOKEN != '' && 'OPENCODE_APPROVE_TOKEN' || 'github-token' }} - CODE_SCANNING_TOKEN_SOURCE: ${{ secrets.PR_REVIEW_MERGE_TOKEN != '' && 'PR_REVIEW_MERGE_TOKEN' || secrets.OPENCODE_APPROVE_TOKEN != '' && 'OPENCODE_APPROVE_TOKEN' || steps.opencode_app_token.outputs.available == 'true' && 'opencode-app' || 'github-token' }} + CODE_SCANNING_TOKEN_SOURCE: ${{ secrets.PR_REVIEW_MERGE_TOKEN != '' && 'PR_REVIEW_MERGE_TOKEN' || secrets.OPENCODE_APPROVE_TOKEN != '' && 'OPENCODE_APPROVE_TOKEN' || 'github-token' }} GH_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_repository || github.repository }} STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} # Exposed so the "openai" provider in opencode.jsonc resolves during the diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index d543bb728..7c51eb2b9 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -645,10 +645,21 @@ def test_workflow_provisions_sandbox_tool_and_reviewer_agent(): assert "collect_open_code_scanning_alerts" in workflow assert ( "CODE_SCANNING_GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || " - "secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || " - "github.token }}" + "secrets.OPENCODE_APPROVE_TOKEN || github.token }}" ) in workflow + # The OpenCode app installation token never carries security-events read, so + # preferring it for the code-scanning alert lookup 403s ("Resource not + # accessible by integration") and defeats the model-unavailable fallback. + code_scanning_token_lines = [ + line for line in workflow.splitlines() if "CODE_SCANNING_GH_TOKEN:" in line + ] + assert code_scanning_token_lines + assert all("opencode_app_token" not in line for line in code_scanning_token_lines) assert "CODE_SCANNING_TOKEN_SOURCE" in workflow + code_scanning_source_lines = [ + line for line in workflow.splitlines() if "CODE_SCANNING_TOKEN_SOURCE:" in line + ] + assert all("opencode-app" not in line for line in code_scanning_source_lines) assert 'GH_TOKEN="$scan_token" timeout "$(check_lookup_api_timeout_seconds)s"' in workflow assert "Open code-scanning alert lookup skipped because no target-repository read token" in workflow assert "production source 또는 package manifest 변경이 없습니다" not in workflow