Files
unleashed-firmware/.github/workflows/pr-comment.yml
T
5cdf9b3374 ci: post Build & analyze report on fork PRs via workflow_run (#1027)
* ci: post Build & analyze report on fork PRs via workflow_run

The Comment PR report step in pr-build.yml fails with 403 "Resource not
accessible by integration" on fork PRs: GitHub gives `pull_request` runs
from forks a read-only GITHUB_TOKEN (they build untrusted code), so
issues.createComment is forbidden and the whole f7 build check goes red
even though the firmware built fine.

Split delivery from analysis:
- pr-build.yml keeps all build/size/API analysis on the fork's code with
  the read-only token, but instead of commenting it uploads the assembled
  report + PR number as the `pr-report` artifact. Drops pull-requests:write.
- pr-comment.yml (new) is triggered on workflow_run when the build
  completes. It runs from the default branch in the base-repo context, so
  it gets a read-write token even for fork-triggered builds, downloads the
  artifact, and posts/updates the comment. It never checks out or runs PR
  code. The PR number travels via the artifact because
  workflow_run.pull_requests is empty for fork PRs.

Same-repo PRs keep working; fork PRs now get the report comment a few
seconds later, and the build check is no longer falsely red.

Closes #1026

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: harden fork-PR comment workflow after review

Review + simplify pass on the workflow_run split:

- pr-comment.yml: add `actions: read` — an explicit permissions block sets
  unlisted scopes to none, and a cross-run download-artifact needs it; without
  it the download 403s, continue-on-error swallows it, and the comment silently
  never posts. This is the fix that makes the feature actually work on forks.
- pr-comment.yml: skip cancelled/skipped builds in the job `if`, and warn (job
  annotation) when the artifact can't be downloaded, so a genuine failure isn't
  a green check with no comment and no signal.
- pr-comment.yml: add a concurrency group (keyed on the build run id, since
  workflow_run.pull_requests is empty for forks) so a re-run can't race the
  find-or-create into duplicate comments; log incomplete artifact at warning,
  not info; per_page:100 on listComments to cut pagination round-trips.
- pr-build.yml: mark the report upload continue-on-error so a transient upload
  hiccup can't redden an otherwise-green build (the point of the split); fix
  two comment overstatements (push/dispatch summary omits the PR-only API
  section; concurrency also covers pushes to dev).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 19:01:47 +03:00

89 lines
4.1 KiB
YAML

# Post the "Build & analyze" report as a PR comment — including on fork PRs.
#
# The build workflow (pr-build.yml) runs on `pull_request`; for fork PRs GitHub hands it
# a read-only token, so it cannot comment itself. It instead uploads the assembled report
# as the `pr-report` artifact. This workflow is triggered by `workflow_run` when that build
# completes: because `workflow_run` always runs the copy of this file on the DEFAULT BRANCH,
# in the base-repo context, its requested write permissions are actually granted (unlike the
# fork's read-only `pull_request` token). It downloads the artifact and posts the comment.
#
# Security: this job must NOT check out or execute the PR's code. Its only untrusted input
# is the report artifact, which it treats strictly as text to post.
#
# Note: `workflow_run` workflows only ever run from the default branch, so changes here take
# effect once merged to dev and can't be exercised from a PR branch.
name: PR report comment
on:
workflow_run:
workflows: ["Build & analyze"] # must match pr-build.yml's `name:`
types: [completed]
# Cross-run artifact download needs `actions: read`; posting the comment needs
# `pull-requests: write`. An explicit block sets every unlisted scope to `none`.
permissions:
actions: read
pull-requests: write # honored here: this runs in the base-repo context, not the fork's
# Serialize comment runs for the same build so a re-run can't race the find-or-create
# below into duplicate comments. workflow_run.pull_requests is empty for fork PRs, so key
# on the run id (unique per triggering build) rather than the PR number.
concurrency:
group: pr-comment-${{ github.event.workflow_run.id }}
cancel-in-progress: false
jobs:
comment:
name: Post report comment
runs-on: ubuntu-latest
# Only PR builds that actually produced a report: skip push/dispatch (no PR) and
# cancelled/skipped builds (no artifact — would otherwise warn spuriously below).
if: >-
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion != 'cancelled' &&
github.event.workflow_run.conclusion != 'skipped'
steps:
# Cross-run: pulls the artifact from the build's run, not this workflow's.
# continue-on-error so a missing/expired artifact doesn't fail the job; the next
# step surfaces it as a warning instead of silently dropping the comment.
- name: Download report artifact
id: dl
continue-on-error: true
uses: actions/download-artifact@v7
with:
name: pr-report
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- name: Warn if report unavailable
if: steps.dl.outcome != 'success'
run: echo "::warning::pr-report artifact could not be downloaded (outcome=${{ steps.dl.outcome }}); no PR comment posted."
- name: Post or update comment
if: steps.dl.outcome == 'success'
uses: actions/github-script@v9
with:
script: |
const fs = require('fs');
if (!fs.existsSync('pr_report.md') || !fs.existsSync('pr_number.txt')) {
core.warning('Report artifact incomplete; nothing to post.');
return;
}
const marker = '<!-- unleashed-pr-report -->';
const body = fs.readFileSync('pr_report.md', 'utf8');
const issue_number = parseInt(fs.readFileSync('pr_number.txt', 'utf8').trim(), 10);
if (!Number.isInteger(issue_number)) {
core.setFailed('Invalid PR number in artifact.');
return;
}
const { owner, repo } = context.repo;
const comments = await github.paginate(github.rest.issues.listComments,
{ owner, repo, issue_number, per_page: 100 });
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number, body });
}