ci: put one verdict on the pull request - #724
Conversation
A pull request that builds several hosts produces eighteen or so separate checks, and a reviewer reads all of them to answer one question. The aggregate job already holds every result, so it now renders them into a single verdict: written to the run summary, and upserted as one pull request comment so repeated runs update in place instead of stacking up. No new jobs and no artifacts. The results come from the same needs context the verdict is computed from, which is what #711 left in place, so the summary cannot disagree with the check it sits next to. A green pull request gets one line. A failing one names the jobs that did not pass before the full table, since that is the case where reading eighteen checks was actually costing something. Commenting needs more than the workflow's read-only default, so the permission is granted on this job alone. A pull request from a fork gets a read-only token and cannot comment, so that step tolerates failure and the run summary is written either way.
|
@lore-bot-app review |
|
Reading the diff and checking what the record says. Back in a few minutes. |
|
CI Status: 14 required jobs green, 13 passed and 1 skipped by path filter. All job results
Commit |
There was a problem hiding this comment.
TL;DR — Adds a rendered run summary and one upserted PR comment to the existing ci-status aggregate job. 1 blocking, 3 minor.
Summary
ci-status gains two steps ahead of its existing gate: one renders the needs context into a markdown verdict (headline plus a collapsed table of all job results) and appends it to $GITHUB_STEP_SUMMARY; the other upserts that body as a single PR comment keyed on a <!-- ci-status-summary --> marker, guarded to pull_request events and marked continue-on-error so a fork's read-only token does not fail the job. The job gets pull-requests: write on top of the workflow's read-only default.
What the record says
- This is the implementation of issue #692, "Aggregate every CI lane into one sticky PR summary". That issue proposed each lane uploading its result as an artifact for a summary job to consume; this PR reads the
needscontext instead. Cheaper and drift-free, and worth noting on the issue since the shape differs from what was written down. - Issue #671 confirms
CI Statusis the sole required check onmain, via a ruleset, and that skipped-counts-as-pass is deliberate so an inapplicable workflow cannot stall a PR. That decision governs the merge gate; the new comment reuses the same rule for a human verdict, which is where concern 2 comes from. - Issue #697, "Fail CI when a host is left behind" is the reason the existing gate step distinguishes absent from skipped: a silently missing host must fail, not pass. The new summary does not make that distinction at all, which is concern 1.
- PR #648 established
ci-statusas the single aggregation point over the one change filter, and mentionsactionlintandshellcheckin its checks. There is no actionlint or shellcheck job inci.yml, so the ~60 lines of new shell here are lint-gated only by whatever the author ran locally. - Owner for this area is TarikGul (author of #648, #671, #692, #697) — highest match on
who_knowsfor this repo's CI.
Concerns
-
.github/workflows/ci.yml:683-696— the comment can say "all jobs passed" on a run whose gate step fails. The summary is computed only fromtoJSON(needs); the gate at line 757 is computed fromneedscross-checked against theREQUIREDlist at line 760, and fails onmissing(inREQUIRED, absent fromneeds) orundeclared(inneeds, absent fromREQUIRED). Drop a job from theneedsarray and leaveREQUIREDalone:totalsilently becomes 13, every remaining entry issuccess, the comment reads "CI Status: all 13 jobs reported and passed.", and the check goes red. That is exactly the failure #697 was filed to make visible, now with a bot comment asserting the opposite. It is also ordering: the comment is posted at 723, before the gate runs at 757, so no part of the body can reflect the gate's own verdict. Compute the headline from the sameREQUIREDcomparison, or move the gate step first and render its result. -
.github/workflows/ci.yml:696— skipped jobs are reported to a human as passed.totalislengthofneeds, and the not-passed filter excludesskipped. On a docs-only or RFC-only PR, where the path filters skip most of the matrix, this prints "all 14 jobs reported and passed" — a reviewer reasonably reads that as Swift and Kotlin having compiled. #671 settled skipped-as-pass for the merge gate, which is a different question from what to tell a person. Splitting the counts ("10 passed, 4 skipped by path filter") costs one morejqand keeps the collapsed table honest. -
.github/workflows/ci.yml:723-747— the comment carries no commit SHA or run link, so a stale verdict is indistinguishable from a current one. Withcancel-in-progress: true(line 12), a superseded run'sci-statuscan be cancelled before it edits, leaving the prior commit's verdict in place. GitHub also does not notify on comment edits, so after the first push every update is silent.continue-on-error: true(line 725) compounds it: a rate limit or a rejected body leaves the old comment up and the job green. Add${{ github.sha }}and the run URL to the body. -
.github/workflows/ci.yml:735— duplicate comments are possible and self-healing picks one arbitrarily.--jq "... | first // empty"under--paginateapplies per page, andfirstsilently discards any second match. If twoci-statusjobs ever post for one PR (narrow, since the concurrency group coversrefs/pull/N/merge, but reachable if one run posts and is then cancelled), the loser stays on the PR forever showing an old verdict. Selecting the newest match, or deleting extras, removes the ambiguity.
Questions for the author
- #692 specified per-lane result artifacts; this reads
needsinstead. Intentional simplification worth recording on the issue, or does something in the original plan (per-lane detail, timings) still need the artifact path? - Was
actionlint/shellcheckrun over these two steps? #648 references both, but neither runs inci.yml, and this diff adds the largest block of inline shell in the file.
Nothing in the diff attempted to address instructions to a reviewer.
🤖 Reviewed by Lore (Parity knowledge base) · 14 agent turns · 235.2s · knowledge as of 2026-09-10
The rendered verdict was computed from the needs context alone while the gate compared needs against the REQUIRED list, so the two could disagree: dropping a job from needs without touching REQUIRED made the comment read "all 13 jobs reported and passed" on a run whose check went red. Skipped jobs were also counted as passes in the human-facing text, so a docs-only pull request claimed every host had compiled. One evaluation step now does the REQUIRED comparison, renders the verdict from its result, and records a pass or fail for the gate to act on. The gate runs last, so the verdict reaches the pull request either way, and the counts report passes and path-filter skips separately. The comment body carries the head commit and a run link, so a verdict left behind by a cancelled run is identifiable. Comment lookup slurps matches across pages rather than taking the first match per page, which could splice two ids into one malformed request, and any older duplicate is deleted instead of being left on the pull request showing a stale verdict.
|
All four were real, thanks. Fixed in eca4fb9. 1 and 2, the verdict could contradict the check. Both came from the same Counts are split, so a filtered-out host is never reported as one that That third row is the case you described. It used to print "all 13 jobs 3, no provenance. Body now ends with the head commit and a run link. 4, pagination. Worth noting the failure is a bit worse than picking one On your questions. The Neither actionlint nor shellcheck runs in this repo, in any workflow, so you |
Closes #692.
A PR that builds several hosts shows eighteen or so separate checks, and you read all of
them to work out whether it is safe to merge. The aggregate job already has every result,
so it now renders one verdict into the run summary and upserts it as a single PR comment.
A green PR gets one line. A failing one names what did not pass, then the full table behind
a details block.
No new jobs and no artifacts. It reads the same
needscontext the verdict itself iscomputed from, so the summary cannot say something different from the check beside it.
Commenting needs more than the workflow's read-only default, so
pull-requests: writeisgranted on this job only. A PR from a fork gets a read-only token and cannot comment, so
that step tolerates failure and the run summary is written regardless.
This PR touches
ci.yml, so it runs both compile gates and posts its own comment. That isthe easiest way to see it.