Skip to content

PR comment - subsystem rollup #8

PR comment - subsystem rollup

PR comment - subsystem rollup #8

name: PR comment - subsystem rollup
# Posts the diagnostic subsystem-size rollup as a PR comment after the
# Build workflow finishes. Lives in a separate workflow on purpose:
# `workflow_run` runs in the base-repo trusted context, so this is the
# only place pull-requests:write is granted. The Build job stays
# read-only, which keeps a compromised toolchain or build-script step
# from minting comments on attacker-supplied PRs.
#
# Fork PRs work too: workflow_run.pull_requests is empty for forks, so
# the resolver falls back to a head-SHA search.
on:
workflow_run:
workflows: [Build]
types: [completed]
permissions:
contents: read
pull-requests: write
actions: read
jobs:
comment:
name: Upsert subsystem rollup comment
runs-on: ubuntu-24.04
if: |
github.event.workflow_run.event == 'pull_request'
&& github.event.workflow_run.conclusion == 'success'
steps:
- name: Download subsystem rollup artifact
uses: actions/download-artifact@v6
with:
name: subsystem-rollup
path: rollup
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- name: Resolve PR number
id: pr
uses: actions/github-script@v9
with:
script: |
const run = context.payload.workflow_run;
// workflow_run.pull_requests is populated for same-repo
// PRs and empty for forks; fall back to a head-SHA search
// so fork PRs still get the comment.
const direct = run.pull_requests || [];
if (direct.length > 0) {
core.setOutput('number', String(direct[0].number));
return;
}
const { owner, repo } = context.repo;
const { data } = await github.rest.search.issuesAndPullRequests({
q: `repo:${owner}/${repo} is:pr is:open ${run.head_sha}`,
});
if (data.items.length === 0) {
core.warning(`no open PR matched head SHA ${run.head_sha}`);
core.setOutput('number', '');
return;
}
core.setOutput('number', String(data.items[0].number));
- name: Upsert comment
if: steps.pr.outputs.number != ''
uses: actions/github-script@v9
env:
# Pass the PR number through the environment instead of
# interpolating it directly into the script body. Template
# substitution happens before the JS engine sees the source,
# so any future change that lets a non-numeric value reach
# this output would otherwise be a script-injection vector.
PR_NUMBER: ${{ steps.pr.outputs.number }}
with:
script: |
const fs = require('fs');
const candidates = [
// download-artifact extracts the common parent of the
// uploaded files, so the current artifact lands flat at
// the destination root.
'rollup/subsystem-rollup.md',
// Keep accepting the older nested layout in case the
// upload path changes again.
'rollup/profiles/kernel-pgo/none/subsystem-rollup.md',
];
const bodyPath = candidates.find(candidate =>
fs.existsSync(candidate)
);
if (!bodyPath) {
throw new Error(
`subsystem rollup markdown not found; checked: ${candidates.join(', ')}`
);
}
const body = fs.readFileSync(bodyPath, 'utf8');
const marker = '<!-- subsystem-rollup-comment -->';
const { owner, repo } = context.repo;
const issue_number = Number(process.env.PR_NUMBER);
const comments = await github.paginate(
github.rest.issues.listComments,
{ owner, repo, issue_number, per_page: 100 },
);
// Strict login match: github-actions[bot] is the only
// identity this workflow ever posts under. A broader
// user.type === 'Bot' filter would let a third-party bot's
// comment shadow ours and silently break the upsert.
const matches = comments.filter(c =>
c.user?.login === 'github-actions[bot]'
&& typeof c.body === 'string'
&& c.body.includes(marker),
);
// Update the most recent match and delete older
// duplicates so a crashed prior run cannot leave stale
// gate state visible on the PR.
matches.sort((a, b) =>
new Date(b.updated_at) - new Date(a.updated_at)
);
if (matches.length > 0) {
await github.rest.issues.updateComment({
owner, repo, comment_id: matches[0].id, body,
});
for (const stale of matches.slice(1)) {
await github.rest.issues.deleteComment({
owner, repo, comment_id: stale.id,
});
}
} else {
await github.rest.issues.createComment({
owner, repo, issue_number, body,
});
}