Files
Dunqing 39e6d91b62 fix(ci): request pull-requests permission for monitor and ecosystem CI tokens (#27574)
## Summary

#27476 scoped the GitHub App tokens in `monitor-oxc.yml` and `ecosystem-ci.yml` to `actions: write` + `issues: write`. The oxc-guard installation does not grant `issues`, so creating the token now fails, and with it the whole trigger job ([failed run on #26929](https://github.com/oxc-project/oxc/actions/runs/38191638085/job/114625444599?pr=26929)):

```
Failed to create token for "oxc-project/oxc,oxc-project/monitor-oxc":
The permissions requested are not granted to this installation. (422)
```

Before #27476 the step requested no specific permissions, so the token got everything the installation has, and `issues` was never checked. When specific permissions are named, GitHub rejects the whole request if any one is missing.

Both workflows use the token only to list, create and update comments and to remove a label on the triggering PR. GitHub accepts the `pull-requests` permission for those calls on a PR, and the app already has it: the old workflow commented on PRs without `issues`, and other workflows request `pull-requests: write` from the same app. This PR requests `pull-requests: write` in place of `issues: write` and keeps the narrow scope from #27476.

`ecosystem-ci.yml` has had the same pair since #27476 but hasn't run for real since, so it would fail the same way on the next `run-ox*-ecosystem-ci` label.

## Notes

- `actions: write` stays as it is. It's needed to dispatch `monitor-oxc` / `oxc-ecosystem-ci`, and the app already has it, because those dispatches worked before #27476.
- `pull-requests: write` can't push or merge, since both need `contents: write`. The token is still limited to two repos and passed only to hash-pinned actions running fixed scripts.

## Verification

- Parsed both workflows as YAML; `git diff --check` is clean.
- No other workflow requests `issues`.
- Not yet run end to end. A `pull_request` `labeled` event runs the workflow from this PR's merge commit, so adding `run-monitor-oxc` to this PR checks the token step before merge. After merge, re-add `run-monitor-oxc` on #26929.

## AI assistance

Investigated and written with Claude Code. The change is two lines and has been reviewed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_0157wqDwm3Jr7bQNw6LPhyZp
2026-10-12 07:54:02 +00:00

160 lines
5.6 KiB
YAML

name: Monitor Oxc
on:
pull_request:
types: [labeled]
issue_comment:
types: [created]
permissions:
pull-requests: read
concurrency:
group: monitor-oxc-${{ github.event.pull_request.number || github.event.issue.number }}
cancel-in-progress: true
jobs:
trigger:
if: >
github.repository == 'oxc-project/oxc' &&
(
(
github.event_name == 'pull_request' &&
github.event.label.name == 'run-monitor-oxc' &&
github.event.pull_request.head.repo.full_name == github.repository
) ||
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
github.event.comment.body == '/monitor-oxc run'
)
)
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
id: pr
with:
script: |
const fromComment = context.eventName === 'issue_comment';
let issueNumber;
let pr;
if (fromComment) {
const user = context.payload.sender.login;
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: user,
});
if (!permission.user.permissions.triage) {
throw new Error(`${user} does not have permission to trigger Monitor Oxc`);
}
issueNumber = context.issue.number;
({ data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: issueNumber,
}));
const commentCreatedAt = new Date(context.payload.comment.created_at);
const headRepoPushedAt = new Date(pr.head.repo.pushed_at);
if (headRepoPushedAt > commentCreatedAt) {
throw new Error('The PR was updated after the trigger command; review it and run the command again');
}
} else {
pr = context.payload.pull_request;
issueNumber = pr.number;
}
core.setOutput('issue-number', issueNumber);
core.setOutput('branch', pr.head.ref);
core.setOutput('head-sha', pr.head.sha);
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
client-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
permission-actions: write
permission-pull-requests: write
owner: oxc-project
repositories: |
${{ github.event.repository.name }}
monitor-oxc
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
id: prepare
env:
BRANCH: ${{ steps.pr.outputs.branch }}
ISSUE_NUMBER: ${{ steps.pr.outputs.issue-number }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const issueNumber = Number(process.env.ISSUE_NUMBER);
const branch = process.env.BRANCH;
const body = `Triggering Monitor Oxc for \`${branch}\`\nhttps://github.com/oxc-project/monitor-oxc/actions/workflows/ci.yml`;
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100,
});
const existing = comments.findLast((c) =>
c.user.login === 'oxc-guard[bot]' &&
c.body &&
(c.body.startsWith('Triggering Monitor Oxc') || c.body.startsWith('## [Monitor Oxc]'))
);
let commentId;
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
commentId = existing.id;
} else {
const { data: created } = await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body,
});
commentId = created.id;
}
core.setOutput('comment-id', commentId);
- uses: benc-uk/workflow-dispatch@d004979be141727c7a17c28f603e2facc40d0855 # v1.3.3
with:
repo: oxc-project/monitor-oxc
workflow: ci.yml
token: ${{ steps.app-token.outputs.token }}
ref: main
inputs: >-
{
"issue-number": "${{ steps.pr.outputs.issue-number }}",
"comment-id": "${{ steps.prepare.outputs.comment-id }}",
"ref": "${{ steps.pr.outputs.head-sha }}"
}
- if: github.event_name == 'pull_request'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
ISSUE_NUMBER: ${{ steps.pr.outputs.issue-number }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(process.env.ISSUE_NUMBER),
name: 'run-monitor-oxc',
});
} catch (error) {
if (error.status !== 404) throw error;
}