diff --git a/.github/workflows/pr-review-merge-scheduler.yml b/.github/workflows/pr-review-merge-scheduler.yml index d32918cf45..d98a72e605 100644 --- a/.github/workflows/pr-review-merge-scheduler.yml +++ b/.github/workflows/pr-review-merge-scheduler.yml @@ -122,6 +122,7 @@ jobs: contents: write id-token: write pull-requests: write + statuses: read env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true GH_TOKEN: ${{ github.token }} diff --git a/docs/doctoring/scheduler-status-read-permission.md b/docs/doctoring/scheduler-status-read-permission.md new file mode 100644 index 0000000000..82e7b0cbf3 --- /dev/null +++ b/docs/doctoring/scheduler-status-read-permission.md @@ -0,0 +1,38 @@ +# Scheduler commit-status read permission (#2120) + +The organization-required scheduler selected `github.token` for same-repository +reads but omitted `statuses` from `scan-pr-queue.permissions`. The private +consumer's combined-status GET consequently failed with HTTP 403 before a merge +verdict. `checks: read` does not grant classic commit-status access. + +Source baseline: `fb17ef556f94f673234aa557254ae52779e9a7b0`. +Consumer evidence: ContextualWisdomLab/late-life-anxiety-reanalysis#10, +head `3d1e3ae56e3ef6ca0a995b6082c4f4a13629e0f6`, run `34698738407`, +job `103566634488` (2026-09-12). The reported failing endpoint is +`GET /repos/{repository}/commits/{head}/status`. + +The repair adds only `statuses: read` to the existing scan job. Workflow defaults, +mutation credentials, cross-repository credential selection and fail-closed API +errors remain intact. It adds no status publication or App installation grant. +The existing credential-contract test now requires exactly `read` in that job's +permission block; its RED revision is `9521b6771`. + +Validation uses the existing pytest workflow/credential/status suites and +Actionlint's workflow validation. Local tests prove the declared contract, not a +hosted permission grant. After protected integration, validate a newly loaded +central source SHA and the consumer's exact current head: the combined-status +request must succeed, and missing checks or substantive failures must still block +merge. For reusable callers, every caller permission ceiling must also admit +status reads; the inspected consumer PR head has no `.github` tree, so do not +invent a repository-local caller or modify App permissions to compensate. + +Next integration review: 2026-09-13, because this prevents the current private +consumer's mandatory scheduler from evaluating status evidence. #2116's HWPX +classification remains a separate bootstrap repair. Reverting this one-line grant +restores the pre-existing 403 behavior; it is not a viable consumer repair. + +## Reference + +GitHub. (n.d.). *REST API endpoints for commit statuses: Get the combined status +for a specific reference*. Retrieved September 12, 2026, from +https://docs.github.com/en/rest/commits/statuses#get-the-combined-status-for-a-specific-reference diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 321d25bd57..d68a8e2e2c 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -2371,6 +2371,11 @@ def test_merge_scheduler_uses_escalating_mutation_credentials(): encoding="utf-8" ) + scan_job = workflow.split(" scan-pr-queue:\n", 1)[1] + permission_block = scan_job.split(" permissions:\n", 1)[1].split(" env:\n", 1)[0] + status_permissions = re.findall(r"^ statuses: (\w+)\s*$", permission_block, re.MULTILINE) + assert status_permissions == ["read"], "same-repository status evidence needs read-only permission" + assert "id-token: write" in workflow assert "Exchange OpenCode app token for scheduler mutations" in workflow assert "secrets.PR_REVIEW_MERGE_TOKEN" in workflow