Skip to content

fix(scripts): an empty ci run list is an unknown, not ten unverified commits - #6479

Closed
macanderson wants to merge 1 commit into
mainfrom
worktree-job-58b53b7f-main-verified
Closed

fix(scripts): an empty ci run list is an unknown, not ten unverified commits#6479
macanderson wants to merge 1 commit into
mainfrom
worktree-job-58b53b7f-main-verified

Conversation

@macanderson

@macanderson macanderson commented Sep 9, 2026

Copy link
Copy Markdown
Owner

What & why

scripts/check-main-verified.sh filed a false alarm this afternoon. At
16:58 UTC it reported the ten newest commits on main as having no ci run
at all and opened this issue. Every one of those ten commits has a completed,
successful ci run:

cd7d53c9 completed success 2026-09-09T16:57:59Z   <- reported "missing"
a3a62948 completed success 2026-09-09T07:12:01Z   <- reported "missing"
bfc63d0b completed success 2026-09-09T06:17:43Z   <- reported "missing"
...all ten

The script guards its commit read for emptiness and never guards its
run read. The matching loop opens every commit at verdict="missing" and
only a row for that commit moves it, so a run list that came back with zero
rows did not report one absence — it reported the whole window as absent at
once, with no row anywhere able to contradict it. gh exits 0 on an empty
page, so the existing if ! runs=... check for a failed call never saw it.

A read that returned nothing cannot tell "no ci run exists for any of these
commits" from "the API declined to list them", and those are opposite states.
The script's own header already decided which way that resolves — "It fails
OPEN, at every unknown"
— because a monitor that fabricates blocks merges
during exactly the incident its repair has to land in, and one that files
falsely is one nobody reads the second time. An empty page is now one of those
unknowns, and it is named in the header beside gh being absent and the API
being unreachable.

Only the empty page. A list that carries rows but none for a given commit
is the outage this script was written to catch, and still reports. A
negative-control case pins that direction so the new guard cannot be satisfied
by refusing to answer whenever a commit has no run of its own.

Closes #6475

The witness

  • This PR includes a witness test (fails on main, passes here)

scripts/test-main-verified.sh, run against this branch:

result
before the fix 35 passed, 2 failed
after the fix 37 passed, 0 failed

The two failing assertions are an empty run list is UNKNOWN, not every commit missing and ...and names an empty run list as the reason. The third new
case — a run list carrying nothing for the commit is still unverified
passes on both sides by design: it is the negative control, and it is what
stops the fix being a blanket "answer nothing when in doubt".

Verified against the real repository as well: ./scripts/check-main-verified.sh
on this branch prints OK — each of the last 10 commit(s) on main has a completed ci run. and exits 0, which is the state the issue claims otherwise.

The gate

  • cargo fmt --check — via make guards-fast
  • workspace clippy and the workspace suite — no Rust in this diff; CI runs both
  • Docs updated where behavior changed — the script's own fail-open header now names the empty run list
  • CLA signed
  • Closes #6475 appears both above and as a commit trailer

make guards-fast, make prose, make line-citations and make shellcheck
all pass on this branch. No test was deleted.

Fix over file

  • Extra fixes in this PR: none. I checked scripts/check-ci-tests.sh,
    which reads the same run list the same way — it is not affected: with
    an empty list its verdict stays empty and it already exits 0 through
    unknown, so it fails open as intended.
  • Filed, with the reason a fix could not ride this PR: An empty releases read makes check-releases-published report every tag as never published #6481. Sweeping
    the sibling guards after this fix turned up the same shape in
    scripts/check-releases-published.sh: its releases read is unguarded, and
    an empty page makes $published empty, so every non-grandfathered tag
    past the grace window reports as absent. It did not ride this PR because
    the obvious guard collides with the script's own purpose — "many tags,
    zero releases" is also the genuine catastrophic finding it exists to
    report, and nothing in the payload separates a broken read from a real
    total failure. Which cost to pay is a maintainer call, so it is written up
    with the three options rather than decided here.

Anything reviewers should know?

Unrelated to this diff, and reported rather than fixed because it is an AWS
IAM change outside this repository: the docs site deploy has failed on every
push to main since 2026-09-08 23:25 UTC
(three consecutive runs of
docs). stella.oxagen.sh has not been updated since.

User: arn:aws:sts::916294258235:assumed-role/gha-deploy-stella/GitHubActions
is not authorized to perform: ssm:SendCommand on
arn:aws:ec2:us-east-1:916294258235:instance/i-094fcb34c7e715cf8
because no identity-based policy allows the ssm:SendCommand action

The role needs ssm:SendCommand on that instance. Nothing in this tree can
grant it.

…commits

`check-main-verified.sh` guarded its commit read for emptiness and never
guarded its run read. The matching loop opens every commit at `missing` and
only a row for that commit moves it, so a run list that came back with zero
rows did not report one absence — it reported the entire window as absent,
with no row anywhere able to contradict it. `gh` exits 0 on an empty page, so
the existing check for a failed call never saw it.

On 2026-09-09 at 16:58 UTC one such page filed against the ten newest commits
on `main` at once. Each of them had a completed, successful `ci` run: the same
query returned all sixty rows twenty minutes later, and a live run of the
fixed script now reports every one of those ten verified. A read that returned
nothing cannot tell "no `ci` run exists for any of these commits" from "the
API declined to list them", and those are opposite states.

The script's own header already decided this — every unknown exits 0 and says
so — because a monitor that fabricates blocks merges during exactly the
incident its repair has to land in, and one that files falsely is one nobody
reads the second time. An empty page is now one of those unknowns, named in
the header beside the others.

Only the empty page. A list that carries rows but none for a given commit is
the outage this script was written to catch, and still reports: the new
negative-control case pins that direction so the guard cannot be satisfied by
refusing to answer whenever a commit has no run.

Witness: `scripts/test-main-verified.sh` — 35 passed, 2 failed before the
change, 37 passed, 0 failed after.

Closes #6475

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @macanderson, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 4 days and 1 hour by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR prevents check-main-verified.sh from turning an empty, successful GitHub API response into false findings for every commit in the window. It adds a fail-open guard with an explicit diagnostic and regression tests covering both the new unknown state and the existing missing-run behavior.

Flow diagram for empty CI run list handling

flowchart TD
    A["check-main-verified.sh reads commits and CI runs"] --> B{"gh run list succeeded?"}
    B -- "no" --> U["unknown: API unavailable"]
    B -- "yes" --> C{"run list is empty?"}
    C -- "yes" --> E["unknown: empty run list"]
    C -- "no" --> D{"matching CI run for each commit?"}
    D -- "yes" --> O["OK"]
    D -- "no" --> M["report unverified commit"]
Loading

File-Level Changes

Change Details Files
Treat an empty GitHub Actions run-list response as an unknown and fail open instead of marking every inspected commit unverified.
  • Add an explicit zero-row guard after the successful gh run list read.
  • Emit an UNKNOWN diagnostic that distinguishes an empty response from a genuine missing-run finding.
  • Update the script header to document empty run lists as a fail-open condition.
scripts/check-main-verified.sh
Add regression coverage for empty responses while preserving detection of commits absent from a non-empty run list.
  • Assert that an empty run fixture exits as UNKNOWN and identifies the empty-list reason.
  • Add a negative-control test confirming a non-empty list with no row for a commit remains unverified.
scripts/test-main-verified.sh

Assessment against linked issues

Issue Objective Addressed Explanation
#6475 Ensure check-main-verified.sh does not treat an empty GitHub Actions run-list response as evidence that every recent main commit lacks verification.
#6475 Continue reporting commits with no matching ci run when the API returns a non-empty run list, while distinguishing that condition from an unknown API response.
#6475 Ensure the affected commits on main obtain completed ci runs, including by redispatching genuinely missing runs where necessary. The PR changes monitoring behavior and tests the empty-response case, but it does not dispatch or re-dispatch workflow runs. It assumes the reported commits already had successful runs and therefore does not itself establish verification for genuinely missing commits.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@macanderson

Copy link
Copy Markdown
Owner Author

Answering the ❌ row in Sourcery's assessment

Ensure the affected commits on main obtain completed ci runs, including by redispatching genuinely missing runs where necessary.The PR changes monitoring behavior and tests the empty-response case, but it does not dispatch or re-dispatch workflow runs. It assumes the reported commits already had successful runs and therefore does not itself establish verification for genuinely missing commits.

This row reads #6475 as a report of a real outage. It was not one, and there is nothing to re-dispatch — so the objective is not deferred or out of scope, it is vacuous for this issue. The assumption Sourcery names is not an assumption; it is the measurement the PR is built on.

Every one of the ten commits #6475 names already had a completed, successful ci run at the moment the issue was filed. The issue was created at 16:58:16 UTC. Running the script's own query — unchanged, same workflow, same branch, same limit:

$ gh run list --workflow ci.yml --branch main --limit 60 \
    --json headSha,status,conclusion,createdAt
cd7d53c9 completed success 2026-09-09T16:57:59Z
a3a62948 completed success 2026-09-09T07:12:01Z
bfc63d0b completed success 2026-09-09T06:17:43Z
8c10e917 completed success 2026-09-09T01:46:46Z
508b6a08 completed success 2026-09-09T01:21:26Z
c5c9e171 completed success 2026-09-09T00:43:26Z
778919f9 completed success 2026-09-09T00:19:57Z
42c5e7fa completed success 2026-09-08T23:54:56Z
bb77ca5e completed success 2026-09-08T23:25:57Z
7760b663 completed success 2026-09-08T23:25:52Z

Nine of those ten concluded hours before the issue was filed; the tenth, cd7d53c9, was created at 16:57:59 and has since concluded success. A workflow_dispatch for any of them would start a second run of a suite that already answered.

The issue's own definition of done is already satisfied, and the check now says so. ./scripts/check-main-verified.sh on this branch, against the live repository:

check-main-verified: OK — each of the last 10 commit(s) on main has a completed ci run.
exit: 0

That is the same script, reading the same API, reaching the opposite verdict from the one that filed the issue — which is precisely the defect. So the honest reading of that DoD line is that it was never unmet: the monitor asserted an absence that was not there.

Why the row is worth answering rather than silently accepting. Treating it as owed work would mean dispatching ten redundant full-workspace CI runs to "establish" verification that already exists — spending an hour of runners to answer a question with an answer already on file. That is the cost the false alarm imposes, and re-dispatching would be paying it a second time.

The one thing genuinely worth checking is whether a future real outage still reports, and it does: the negative-control case a run list carrying nothing for the commit is still unverified passes on both the old and new code by design. A list that carries rows but none for a given commit is the outage this script exists to catch, and the new guard covers only the zero-row page.

@macanderson

Copy link
Copy Markdown
Owner Author

Closing as a duplicate of #6477, which merged at 21:12:57Z and reached the same diagnosis and the same fix: the run read had no emptiness guard, gh exits 0 on an empty page, and the verdict loop defaults every commit to missing, so one blind read became ten findings.

#6477 is the better of the two and nothing here is worth salvaging on top of it. Its test suite covers both of my new cases and adds a third — that an empty read files no issue at all — and its write-up settles one thing mine only asserted: it checked directly that the same query under an invalid token exits 1, which rules out a permission fault and leaves the empty-answer-with-exit-0 shape as the only explanation.

How this collided, since the machinery exists to stop it. I ran ./scripts/issue-claim.sh check 6475 at 20:48:10Z and it correctly reported the issue unclaimed — #6477 was not opened until 20:54:26Z, and no claim comment had been posted against it. I then claimed it and started work. What I did not do was re-run the check before gh pr create, which is exactly what AGENTS.md asks for:

Run it before writing code and again before opening the PR. The gap between those two is enough: a peer's PR can merge inside one issue's worth of work, and then the check that was clean at the start is stale at the end.

The second run would have found #6477 with Closes #6475 open against it and stood me down. The check was not wrong and it was not missing — it was asked once when it needed asking twice.

What does not close with this PR, because neither is covered by #6477:

@macanderson macanderson closed this Sep 9, 2026
@macanderson
macanderson deleted the worktree-job-58b53b7f-main-verified branch September 9, 2026 21:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

main carries a commit nothing verified

1 participant