Repository navigation
fix(pm): ci-failure resolves a red lane's Lane verdict to its red gates' own commands - #23246
Merged
objectstack-fleet[bot] merged 2 commits intoOct 13, 2026
Merged
Conversation
…es' own commands Since every gate step of a lint/typecheck lane runs under continue-on-error, a red gate's API conclusion is success and the lane's one failed step is Lane verdict, whose name resolved once per lane to the verdict command (exit 3 locally). The red gate names are read from the verdict's sentences in the annotations and the job log's error lines, and each resolves to the gate's own run: block. A red verdict that names no red gate says so; an unparseable one is reported as UNRESOLVED, never as the verdict command. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zwsLMxQBwHUYJgPyrpEvx
…rowing on it Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zwsLMxQBwHUYJgPyrpEvx
objectstack-fleet
Bot
deleted the
claude/issue-23223-ci-failure-lane-verdict
branch
October 13, 2026 11:53
This was referenced Oct 13, 2026
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 #23223
Clause-②: no
What this changes
Since #23191, every gate step of a
Lint · */Type Check · *lane runs undercontinue-on-error: true. A red gate's jobs-APIconclusionis thereforesuccess, so the only failed step of a red lane is its last one,Lane verdict.scripts/pm/ci-failure.mjsresolved that step name throughstepBodies. The name exists once per lane, so thesubstituteblock printednode scripts/ci/lane-verdict.mjs12 times. Run locally, that command exits 3 and reproduces nothing.Now, when the failed step is
Lane verdict, the tool reads the red gate names from the verdict's own sentences (gate step "NAME" (id ID) failed). It resolves each name throughstepBodiesto that gate's ownrun:block. Thesubstituteblock prints those commands. The verdict's own command is never offered.Only one file changes:
scripts/pm/ci-failure.mjsand its--self-test. Nothing under.github/workflows/**,scripts/ci/lane-verdict.mjsor.claude/**is touched.classifyAnnotationgains one kind,lane-verdict. It recognises the five sentences thatlane-verdict.mjs'srenderVerdictwrites as error annotations:red,unread,did-not-run,fail-fastandrefusal. Forred, it keeps the gate name and id. Everything else still classifies as before.assertionStatusandverdictOfread the new kind as content, the same as the oldother, so the exit table does not move.readLaneVerdicttakes the union of those sentences from two sources, deduplicated: the check-run's annotations and the job log's error lines. The log is already fetched for this family, so it costs no extra request. GitHub's documentation says one step can create only a limited number of error annotations. A lane with more red gates than that limit would name only some of them in the annotations; the log names them all. That limit is NOT measured here, and the union makes the result independent of it.resolveFailedStep: every step exceptLane verdictresolves by its own name, unchanged. ForLane verdict,reproholds the red gates' hits, each carryinggate: { name, id }, andlaneVerdictholds the full reading. Both appear in--json.renderSubstituteis the old inlinesayRepro, now a pure function so the self-test can pin the printed lines. When the verdict names no runnable red gate, it says why, in one of three ways:stepBodiesnow filters a newworkflowStepsscan. The output is identical, and the existing pins still hold.Proof on the measured sample
Run 38373082677 (PR head
5301220aa), check-run / job 115214618941,node scripts/pm/ci-failure.mjs --run 38373082677. The tool has no--jobflag, so--runwas used.Before, on
mainat197fbe2c04:After, on this branch:
diff before aftershows only three changes: the twootherheads for that sentence becomelane-verdict, and the substitute block above changes. The other three failing checks on that sha render byte-identical. The walk still exits 2 (UNDETERMINED) both times, because none of the four failing checks carries a file-anchored assertion. The exit table is unchanged on purpose.Self-test, and ablation that proves it can fail
A new battery,
Lane verdict: the red gates, not the verdict (#23223), has 22 cases. ThestepBodies / resolveStepbattery goes from 8 to 9 cases, and the battery floor from 14 to 15.The battery imports
scripts/ci/lane-verdict.mjsand drives its realjudgeLaneandrenderVerdict. The parser is therefore pinned to what the producer actually prints, so a wording change there turns this battery red. No rendered line is printed: they are workflow commands, and this self-test runs on a runner.The cases pin the card's three requirements:
continue-on-errorand noid:) says it names no red gate. It does not resolve to nothing, and it does not read as "resolves to norun:block".failureresolves exactly as before, line for line.The cases also cover the measured specimen, all five sentence shapes, the UNRESOLVED and NOT READ cases, the log/annotation union, a red gate that is missing from the local tree, and a gate name containing quotes and parentheses. A live pin requires that every step in the real workflow tree that runs the verdict is named
Lane verdict(12 of them today). A lane that renames its verdict step therefore turns the self-test red instead of silently bringing the defect back.Ablations were run on committed
77480b13cethroughscripts/ablation-replace.mjs. In each leg the anchor hit 1 to 0, the blob changed, and the restore was proven blob == HEAD with an emptygit diff HEAD:resolveFailedStepstops treatingLane verdictspeciallyredsentence pattern no longer matchesMy first leg-1 run crashed the harness on
r.gate.nameinstead of reporting the failure. Two accessors are now null-safe, so a regression is reported rather than thrown (77480b13ce).Local gates, at
77480b13cenode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILEreports 30 derived, 30 run, 0 NOT-MEASURED, 0 UNRUN, with an exit code recorded for every one (all 0). That count includespnpm check:pm-dispatch-gates(EXIT=0, 397 s),node scripts/pm/ci-failure.mjs --self-test,node scripts/check-self-test-workflow-commands.mjs(its end-to-end case runs this self-test and finds no runner-parsed line),pnpm check:nul-bytesandpnpm check:entry-guard.node scripts/pm/check-governed-merges.mjs --self-test(EXIT=0, 500 assertions; it reads this file's unchangedPROXY_REARM_GUARDline).pnpm lintiseslint . --no-inline-config, andeslint --print-config scripts/pm/ci-failure.mjsresolves a config for it, so the file is inside the lint population.--no-inline-config --format json scripts/pm/ci-failure.mjsreports 1 file, 0 errors, 0 warnings.parserOptions.project(eslint.config.mjsnever enables type-aware linting), and the two rules that apply are per-file syntactic rules. So this diff cannot change the verdict for any file it does not touch.scripts/pm/**publishes nothing (skip-changeset).Acceptance notes
resolveStepbehaviour ("picking one would be a silent guess"). One example:Sweep the built closure for vanished declarationsis a step inci.ymlbuild-core and in twolint.ymllanes, and the two lint steps share oneid:. So the annotation's id cannot narrow it either. Narrowing by job would need a mapping from job display names to job ids, which is outside this card.references/platform-readings.mdhalf is not in this PR: it is a governed.claude/**path, and the claim leaves it to skills(pm-dispatch): a text-only DELIBERATE CORRECTION is confirmed by the seat's ACCEPT quoting before/after; the second same-head subagent review is dropped for that class (#23177 ruling B, item 4) #23201 or a skills card. The platform reading it would record is quoted in this file's header instead: undercontinue-on-error, a red gate's APIconclusionissuccessand only the steps-contextoutcomeisfailure.Lint & Repo Gatesaggregator row (Verify every lint lane succeeded) still offers its own aggregator script as the substitute. It names the red lane in an annotation (lint lanelint-repo-toolingconcludedfailure), and the lane's own row, fixed here, carries the gate commands. Noted only.Generated by Claude Code