Repository navigation
fix(pm): ci-failure reads aggregator roster jobs from their declared rosters, so the lint.yml verdicts rank last and point at their lanes - #23253
Merged
objectstack-fleet[bot] merged 2 commits intoOct 13, 2026
Conversation
…rations
isRosterJob's step-name pattern knew the two ci.yml shard-attestation
verdicts and missed the two lint.yml aggregators ("Verify every lint lane
succeeded", "Verify every type-check lane succeeded"). Those rows ranked
first, had a job log fetched for nothing, and offered the verdict's own
OS_NEEDS heredoc as the local reproduction.
Aggregators are now the jobs whose job-level env declares
OS_AGGREGATOR_MEMBERS (the key check-aggregator-roster holds equal to
needs:, imported), and the roster verdict is the step that reads the needs
context. A roster verdict step resolves to no command of its own; its
substitute names the member lanes that did not succeed, read from the
check-runs already listed. The self-test pins both lint.yml aggregator
names, the two shard gates, and two real-tree controls (Verify build
outputs, Test Core's own ceiling gate) that stay unclassified.
Claude-Session: https://claude.ai/code/session_012zwsLMxQBwHUYJgPyrpEvx
Co-authored-by: Claude <noreply@anthropic.com>
…d of crashing Claude-Session: https://claude.ai/code/session_012zwsLMxQBwHUYJgPyrpEvx Co-authored-by: Claude <noreply@anthropic.com>
objectstack-fleet
Bot
deleted the
claude/issue-23249-ci-failure-aggregator-roster
branch
October 13, 2026 12:49
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #23249
Clause-②: no
What changed
scripts/pm/ci-failure.mjsonly.isRosterJobknew roster jobs by the step-name pattern/^Verify .* results$/. That pattern matched the two ci.yml shard-attestation verdicts and missed both lint.yml aggregators,Verify every lint lane succeeded(lint.yml:363) andVerify every type-check lane succeeded(lint.yml:6735). Both line numbers were re-measured onmainatafbf960c26.Following the card's stated preference, the names now come from the workflows' own aggregator roster instead of a wider pattern:
aggregatorRoster(workflows): an aggregator is a job whose job-levelenv:declaresOS_AGGREGATOR_MEMBERS.MEMBERS_KEYanddeclaredIdsare imported fromscripts/check-aggregator-roster.mjs, the gate that holds that declaration equal toneeds:, so the key has one spelling and two readers. The import is safe beforepnpm install: that module's YAML parser loads only inside its own reader. I ran this file's self-test from agit archivetree with nonode_modulesanywhere above it, and it exited 0.needscontext in a workflow expression. A step's job being an aggregator is not enough, becauseTest Corealso runsCheck the per-package suite-duration ceilings. That gate carries its own assertion, so its log is still the answer when it is the red step.workflowJobs(text): a hand-rolled job scanner, built the same way asworkflowStepsand for the same reason (noyamldependency). I cross-checked it once against theyamlparser over every workflow file at5a9363ae28. Over 40 files and 75 jobs, all 4 roster declarations agree on id, display name and members, with 0 mismatching files.rankFailing), labelled ROSTER JOB, no job log fetched (shouldFetchJobLog). A roster verdict step resolves to no command of its own. Its substitute names the member lanes that did not succeed. Those come from the check-runs the walk had already listed (memberLanes, matched by job display name and run id), so this costs no extra request.Bounded in-place fix, declared
Before this change, the
Test CoreandDogfood Regression Gateroster rows offerednode scripts/check-shard-attestation.mjs --verify ... --dir "$OS_ATTEST_DIR" ...as the local reproduction. That command reads runner-only artifacts and env, so it reproduces nothing locally either. With one roster classification, these rows now get the same member-lane substitute. All four conditions for an in-place fix hold:run:reproduces nothing.ci-failureself-test only.The self-test pins it.
Before and after,
node scripts/pm/ci-failure.mjs --run 38373082677Both outputs come from the same run. Before is
afbf960c26(main); after is this branch. The exit is 2 / UNDETERMINED both times: no failing check on that sha carried a file-anchored assertion, either before or after.Lint & Repo Gates·Test Core (1/6)·Lint · repo & tooling gates·Test CoreTest Core (1/6)·Lint · repo & tooling gates·Test Core·Lint & Repo GatesTest Core)Test Core,Lint & Repo Gates)The before
Lint & Repo Gatessubstitute. The excerpt stops just before the heredoc opener line, which is a shell here-document start readingverify-lanes.mjs:The after row:
The
Test Corerow now reads✗ "Test Core (1/6)" (test) — failure; its own row is abovewhere it used to print the--verifycommand.Self-test
node scripts/pm/ci-failure.mjs --self-testexits 0. Two batteries moved:isRosterJobis now 19 cases (was 5).roster verdict: the substitute names the member lanes, 13 cases.In both batteries, the pinned count equals the count registered. The card's three pins run against the real tree:
Verify every lint lane succeededresolves toLint & Repo Gates.Verify every type-check lane succeededresolves toTypeScript Type Check.Verify build outputs(ci.ymlbuild-core) exists in the tree and stays unclassified. A second control:Test Core's own ceiling gate exists and stays unclassified.The tests also hold this scan to
check-aggregator-roster'sREQUIRED_AGGREGATORSfloor (found, under its context, with a verdict step), and require every declared member to resolve to a named job.Ablations are one-shot runs: committed first, mutated with
scripts/ablation-replace.mjs(anchor 1→0, blob moved), restored with the HEAD blobfe3f35f3b26bmatched andgit diff HEADempty. No permanent test file was added.rosterVerdictFor^Verify(the alternative the card warned about)The first attempt at the first ablation landed but crashed the harness instead of reporting. The lanes fixtures had spread an absent verdict into a truthy one with no members. I fixed that in
5a9363ae28(fixtures attach lanes only to a step that resolved as a roster verdict), and the reading above is from the re-run.Gates, at
5a9363ae28node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 30 commands. All 30 ran and exited 0, includingpnpm check:pm-dispatch-gates: fast tier, 1833 cases, 379.9 s.--ranreconciliation:30 derived, 30 run, 0 NOT-MEASURED, 0 UNRUN. That zero is derived from the recorded exit codes.Test Coreshard job the derivation schedules by path. That is CI's own shell, with no local invocation.eslint --print-config scripts/pm/ci-failure.mjsapplies 2 rules to it, so it is linted, not ignored.--format jsonreports 1 file linted, 0 errors, 0 warnings, with--no-inline-config.eslint.config.mjsnever enables type-aware linting: there is noparserOptions.projectorprojectService, and this file gets onlyecmaVersion/sourceType. So this diff cannot move the verdict on any untouched file.skip-changeset:scripts/pm/**publishes nothing.Acceptance notes
verdictOfstill counts a roster row in the exit table's denominator, so a walk with a red roster job reads UNDETERMINED even when every member's assertion is in hand. That is the documented "this row is kept so the count is honest", left unchanged. Noted, not filed (no carrier).lint lane ... concluded failure) still classify asother. Members are named from the declared roster instead of by parsing those sentences, because they come from an inline workflow heredoc with no renderer this self-test could import and pin a reader against. Noted, not filed.checkNameMatchermatches a${{ ... }}expression in a member'sname:as any text. A matrix member whosename:has no expression gets GitHub's(values)suffix and would not match. No member has that shape today: of 13 members, 2 are matrix jobs with expressions and 11 are not matrix jobs. Noted, not filed.Generated by Claude Code