Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions .github/workflows/bench-comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Posts the benchmark comparison comment produced by bench-compare.yml.
#
# bench-compare.yml runs on `pull_request`, where fork PRs get a read-only
# token that cannot post comments. This workflow runs on `workflow_run` with
# write permissions, downloads the comment artifact from the triggering run,
# and creates/updates the marker-managed PR comment — for fork and same-repo
# PRs alike.
#
# Security: this workflow has a write token but never checks out or executes
# PR code. The artifact content is treated as data only; the PR number it
# names is validated against the triggering run's head SHA before posting.
name: Benchmark Comment

on:
workflow_run:
workflows: ['Benchmark Comparison']
types: [completed]

permissions:
actions: read
pull-requests: write

jobs:
post-comment:
name: 'Post benchmark comment'
runs-on: ubuntu-latest
if: github.event.workflow_run.event == 'pull_request'

steps:
- uses: actions/download-artifact@v4
with:
name: bench-comment
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
path: comment-artifact

- name: Post PR comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const marker = '<!-- bench-compare -->';

let body = fs.readFileSync('comment-artifact/bench-comment.md', 'utf8');
const prNumber = parseInt(
fs.readFileSync('comment-artifact/pr-number.txt', 'utf8').trim(),
10
);

if (!Number.isInteger(prNumber) || prNumber <= 0) {
core.setFailed(`Invalid PR number in artifact: ${prNumber}`);
return;
}

// The artifact was produced by an untrusted run; confirm the PR it
// names is the one the triggering run actually built.
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
if (pr.head.sha !== context.payload.workflow_run.head_sha) {
core.info(
`PR #${prNumber} head (${pr.head.sha}) does not match the ` +
`triggering run (${context.payload.workflow_run.head_sha}) — ` +
'stale or mismatched artifact, skipping.'
);
return;
}

// GitHub comments cap at 65536 characters; keep the summary
// tables and truncate the tail (full output stays in the job
// summary of the triggering run).
const limit = 65000;
if (body.length > limit) {
body =
body.slice(0, limit) +
'\n```\n\n</details>\n\n> Output truncated — full results in the workflow job summary.\n';
}

const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});

const existing = comments.find((c) => c.body.includes(marker));

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}
55 changes: 14 additions & 41 deletions .github/workflows/bench-compare.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,46 +57,19 @@ jobs:
if: always() && steps.checkout.outcome == 'success'
run: cat "$RUNNER_TEMP/bench-comment.md" >> "$GITHUB_STEP_SUMMARY"

- name: Post PR comment
# This job runs with a read-only token for fork PRs, so it cannot post
# the comment itself. It uploads the comment body as an artifact and the
# privileged bench-comment.yml workflow (workflow_run) posts it — same
# path for fork and same-repo PRs.
- name: Upload comment artifact
if: always() && steps.checkout.outcome == 'success'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const marker = '<!-- bench-compare -->';

const body = fs.readFileSync(process.env.RUNNER_TEMP + '/bench-comment.md', 'utf8');

const headFullName = context.payload.pull_request?.head?.repo?.full_name;
const isFork = !headFullName || headFullName !== context.repo.owner + '/' + context.repo.repo;

if (isFork) {
core.info('PR is from a fork — skipping PR comment (results are in the job summary).');
core.info('--- Comment body start ---');
core.info(body);
core.info('--- Comment body end ---');
} else {
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});

const existing = comments.find(c => c.body.includes(marker));
run: |
mkdir -p "$RUNNER_TEMP/comment-artifact"
cp "$RUNNER_TEMP/bench-comment.md" "$RUNNER_TEMP/comment-artifact/bench-comment.md"
echo "${{ github.event.pull_request.number }}" > "$RUNNER_TEMP/comment-artifact/pr-number.txt"

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
}
- uses: actions/upload-artifact@v4
if: always() && steps.checkout.outcome == 'success'
with:
name: bench-comment
path: ${{ runner.temp }}/comment-artifact/
Loading