From 05b52e7617e87d2fd6892d8d387ff9d6707a110c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 17:24:48 +0900 Subject: [PATCH] fix(opencode): use same-repo status credential --- .../workflows/opencode-review-dispatch.yml | 4 +- CHANGELOG.md | 6 +++ ...ncode-same-repository-status-credential.md | 49 +++++++++++++++++++ tests/test_opencode_agent_contract.py | 10 +++- ...t_pr_review_autofix_nvidia_nim_contract.py | 2 +- 5 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 docs/doctoring/opencode-same-repository-status-credential.md diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index 3bc1ce6d3..d729d57df 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -7925,14 +7925,14 @@ jobs: && needs.validate-pr-metadata.outputs.target_repository != '' && needs.validate-pr-metadata.outputs.head_sha != '' env: - GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }} + GH_TOKEN: ${{ needs.validate-pr-metadata.outputs.target_repository == github.repository && github.token || secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || github.token }} GH_REPOSITORY: ${{ needs.validate-pr-metadata.outputs.target_repository }} PR_NUMBER: ${{ needs.validate-pr-metadata.outputs.pr_number }} PR_HEAD_SHA: ${{ needs.validate-pr-metadata.outputs.head_sha }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} OPENCODE_MODEL_POOL_OUTCOME: ${{ steps.opencode_review_model_pool.outputs.review_status }} COVERAGE_EVIDENCE_RESULT: ${{ needs.coverage-evidence.result }} - OPENCODE_STATUS_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' }} + OPENCODE_STATUS_TOKEN_SOURCE: ${{ needs.validate-pr-metadata.outputs.target_repository == github.repository && 'github-token' || 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' }} OPENCODE_CHANGED_FILES_FILE: ${{ runner.temp }}/opencode-changed-files.txt OPENCODE_ARTIFACT_MANIFEST_SHA256: ${{ steps.seal_artifacts.outputs.manifest_sha256 }} OPENCODE_SOURCE_WORKDIR: ${{ runner.temp }}/opencode-pr-head diff --git a/CHANGELOG.md b/CHANGELOG.md index 47c14f765..f6d77e573 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,6 +45,12 @@ Semantic Versioning where the repository publishes a release. ### Fixed +- Prefer the job-scoped `github.token` when the central OpenCode dispatch + publishes a commit status back to the same `.github` repository. The job's + declared `statuses: write` permission now reaches the endpoint instead of an + unrelated OpenCode App installation token that can lack commit-status write + permission; cross-repository status publication keeps the existing explicit + PAT/App credential chain. - Used the receiving repository's workflow token for same-repository scheduler Actions inventory and read calls, while retaining the established mutation credential chain. An exhausted organization-wide OpenCode App installation diff --git a/docs/doctoring/opencode-same-repository-status-credential.md b/docs/doctoring/opencode-same-repository-status-credential.md new file mode 100644 index 000000000..e2b32c01b --- /dev/null +++ b/docs/doctoring/opencode-same-repository-status-credential.md @@ -0,0 +1,49 @@ +# OpenCode same-repository status credential + +## Operator outcome + +An OpenCode repository-dispatch run targeting `ContextualWisdomLab/.github` +publishes its optional `opencode-review` commit status with the current job's +`github.token`. Cross-repository targets continue to use the configured PAT or +OpenCode App installation token, because `github.token` is limited to the +repository containing the workflow. + +If status publication fails, inspect the logged token-source label and the +endpoint response. Do not weaken the formal exact-head Reviews API verdict or +branch protection: the commit status is complementary evidence. + +## Root cause and decision + +Run 32560612401 declared `statuses: write` for the OpenCode job but selected the +separate OpenCode App token for a same-repository status write. GitHub rejected +`POST /repos/ContextualWisdomLab/.github/statuses/{sha}` with HTTP 403 because +that installation token did not carry commit-status write permission. + +The smallest repair is credential precedence at the existing publication +boundary. Same-repository publication uses `github.token`, whose effective +permissions are already narrowed by the job. Cross-repository publication +retains the established PAT/App chain and the existing neutral path when only a +repository-scoped workflow token is available. No new credential, permission, +provider, retry, or fallback abstraction is introduced. + +This boundary supports SOC 2 and CSAP evidence expectations by preserving +least privilege, explicit credential provenance, exact-head status binding, +and an auditable failure instead of broadening the OpenCode App installation. + +## Verification + +- The contract test requires both `GH_TOKEN` and its logged source to select + `github-token` first only when the target equals the workflow repository. +- The existing cross-repository notice and fail-closed exact-head review path + remain unchanged. +- The complete Python, shell, compilation, docstring, and branch-coverage gates + remain mandatory before merge. + +## APA 7th references + +GitHub. (n.d.). *GITHUB_TOKEN*. GitHub Docs. Retrieved August 22, 2026, from +https://docs.github.com/en/actions/concepts/security/github_token + +GitHub. (n.d.). *Permissions required for GitHub Apps*. GitHub Docs. Retrieved +August 22, 2026, from +https://docs.github.com/en/rest/authentication/permissions-required-for-github-apps diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index aaea3b0eb..3941d3b3a 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -2047,11 +2047,17 @@ def test_opencode_runs_merge_scheduler_after_review_without_repo_local_dispatch( " - name: Dispatch Noema after current-head OpenCode approval", 1 )[0] assert ( - "GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || " + "GH_TOKEN: ${{ needs.validate-pr-metadata.outputs.target_repository == " + "github.repository && github.token || secrets.PR_REVIEW_MERGE_TOKEN || " "secrets.OPENCODE_APPROVE_TOKEN || steps.opencode_app_token.outputs.token || " "github.token }}" ) in status_step - assert "OPENCODE_STATUS_TOKEN_SOURCE" in status_step + assert ( + "OPENCODE_STATUS_TOKEN_SOURCE: ${{ " + "needs.validate-pr-metadata.outputs.target_repository == github.repository && " + "'github-token' || secrets.PR_REVIEW_MERGE_TOKEN != '' && " + "'PR_REVIEW_MERGE_TOKEN'" + ) in status_step assert "steps.opencode_app_token.outputs.available == 'true' && 'opencode-app'" in status_step assert "OPENCODE_CHANGED_FILES_FILE" in status_step assert "OPENCODE_ARTIFACT_MANIFEST_SHA256" in status_step diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index d2d87b9e3..0467e0d70 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -19,7 +19,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "3bc1ce6d385bce569e7a7ba037f149a8f18039d4" +REVIEW_DISPATCH_BLOB_SHA = "d729d57df8bb96ae0702dcee10a08b51c90bc5cf" def _workflow_text(path: Path) -> str: