Repository navigation
Fork PR review events leave labels stale until the hourly sweep #2046
Description
Problem
A review on a fork PR does not update its labels for up to one hour. Examples: #1293 and #1705.
Context
GitHub gives pull_request_review runs for fork PRs a read-only GITHUB_TOKEN, even when the workflow asks for write permissions. The workflow already detects this (isReadOnlyRun && isForkPR(pr)), computes the right label, and then skips the write. The log for #1705 shows this:
PR #1705: CI passing, reviews=2, coderabbit=APPROVED, maintainer=pending, blockers=1 → awaiting-author
PR #1705: fork PR on a read-only run — skipping label mutation
The label changes only when a trusted event runs: a CodeRabbit comment update, a workflow_run of a listed workflow, the hourly sweep, or a push to main. For a maintainer who requests changes on a fork PR, this can be up to 60 minutes. Most PRs in this repo come from forks.
Research
- GitHub documents that fork PRs always get a read-only token on
pull_request_review. There is no setting to raise it. Community discussion #26651, #55940. - GitHub's guidance for labeling fork PRs is a privileged trigger that does not run PR code. Securely using pull_request_target. There is no
pull_request_review_targetevent, sopull_request_targetcannot cover review events. - GitHub Security Lab recommends a two-workflow pattern: an unprivileged workflow handles the untrusted event, and a privileged workflow runs on
workflow_run. Preventing pwn requests, Secure use reference. - The risk to avoid in the privileged workflow is a checkout of PR code, or use of untrusted artifacts. This workflow does neither. It reads only the PR number, and it fetches all PR data with the API. A
workflow_runfollow-up keeps that property if it takes no artifact input.
Proposed fix
- Add this workflow's own name to the
workflow_run.workflowslist. - In the
resolvejob, accept aworkflow_runof this workflow only whengithub.event.workflow_run.event == 'pull_request_review'. This stops a loop: a run triggered byworkflow_runhas the eventworkflow_run, so the next level is filtered out. - Resolve the PR with the existing head SHA fallback. For fork PRs
workflow_run.pull_requestsis empty, and the workflow already handles this. - Take no artifact input. Do not check out code.
- Add tests: a fork review run is followed by a trusted run that writes labels; a
workflow_runof a non-review run is ignored; the loop guard holds.
Alternative: shorten the sweep from hourly to every 15 minutes. It is simple, but it costs 4 times the API calls against a shared 1,000 requests per hour pool. I do not recommend it.
File: .github/workflows/label-pr-review-state.yml. Tests: src/services/__tests__/pr-review-state-workflow.test.ts.
Activity