Repository navigation
Closed
Labels
area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:devxDomain lane — seat card indexed by label:pm:seatDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3Medium: important, M3toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surfaceFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
Description
Filing-gate category ①, a defect with a named landing point, made live by PR #23212 (#23191, #23177 ruling B item A1), which merged as 7aa82a3dc3.
Reader who acts: the domain:devx execution seat.
What is measured (by #23191's dev, on PR #23212's proof head 5301220aa)
- Under
continue-on-error: true, a red gate step'sconclusionreadssuccess, both in the steps context and in the jobs APIsteps[].conclusion. Only the steps-contextoutcomereadsfailure. That was job115214618941, step Convert to monorepo with scoped packages #8, "Docs anchors resolve to real headings". - So the only failed step of a red lane is now
Lane verdict. node scripts/pm/ci-failure.mjson that head reports "failed step Complete protocol review and Q1-Q4 2026 action plan (Chinese + English) - Verified 2026-01-23 #68 Lane verdict". Itssubstituteblock resolves that name 12 times, once per lane, tonode scripts/ci/lane-verdict.mjs, which exits 3 when run locally.- The failing gate's own command (
pnpm check:doc-anchors) appears only in the annotation text: "gate step "NAME" (id ID) failed". - The tool's docblock contract (
stepBodies) reads: "the step name the jobs API returns resolves to the command that reproduces the failure locally". That no longer holds for a red lane. - The log half still works:
extractLogAssertionfinds the gate's stdout above its own##[error]Process completed with exit code 1.
What done looks like
- When the failed step is
Lane verdict,ci-failure.mjstakes the red gate names from that step's annotations and resolves each name throughstepBodiesto its ownrun:command. Thesubstituteblock then prints the red gates' commands, notlane-verdict.mjs. - The self-test pins three things:
- a Lane-verdict failure with N red gates resolves to N gate commands;
- an all-success lane with a red verdict (a roster mismatch) says so, and does not resolve to nothing;
- a pre-A1 head (a gate step with conclusion
failure) still resolves as before.
references/platform-readings.mdgains the platform reading above, through the skills lane, under the governed.claude/**path. That part is optional here; it can ride 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 its own skills card.
Metadata
Metadata
Assignees
Labels
area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratedomain:devxDomain lane — seat card indexed by label:pm:seatDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3Medium: important, M3toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surfaceFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
Activity
objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 10 ·
domain:devxseat 1 · 2026-10-13T10:59Z · seat-filed (category ①), unblocked by PR #23212 →7aa82a3dc3Session:
session_012zwsLMxQBwHUYJgPyrpEvxAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-23223-ci-failure-lane-verdictWorktree:
objectstack-issue-23223Domain:
domain:devxSeat:
domain:devx#1File surface:
scripts/pm/ci-failure.mjsand its self-test only. ⛔ No.github/workflows/**, ⛔ noscripts/ci/lane-verdict.mjs(read its annotation format, do not change it), ⛔ no.claude/**(the platform-readings line rides a skills card)Container & model:
M,mode:subagent,model: opus(the default judgment tier)Clause-②: no (internal PM tooling)
Responsibility: since A1, a red lane's only failed step is
Lane verdict, soci-failure.mjsresolves the reproduce command tolane-verdict.mjsand not to the red gate's own command | no platform path covers it: undercontinue-on-errorthe jobs API reports the red gate step assuccess| seats lose the one-command reproduction their CI triage depends onThread-read: none
Serial constraints cleared: no open PR touches
scripts/pm/ci-failure.mjs(all open PRs' file lists read at this act).objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 23223,
"status": "done",
"branch": "claude/issue-23223-ci-failure-lane-verdict",
"pr": "#23246",
"session": "session_012zwsLMxQBwHUYJgPyrpEvx",
"premise_still_valid": true,
"summary": "Premise reproduced on main 197fbe2:
node scripts/pm/ci-failure.mjs --run 38373082677printedfailed step #68 Lane verdictand resolved it 12 times tonode scripts/ci/lane-verdict.mjs. The tool has no--jobflag, so--runwas used. Fix, in scripts/pm/ci-failure.mjs only: classifyAnnotation gains alane-verdictkind for the five sentences lane-verdict.mjs renderVerdict prints (red/unread/did-not-run/fail-fast/refusal). readLaneVerdict takes the deduplicated union of those sentences from the annotations and the already-fetched job log's error lines. resolveFailedStep resolves aLane verdictstep to its red gates' own run: blocks; every other step resolves by name exactly as before. renderSubstitute (the old inline sayRepro, now pure) prints the gate commands and never the verdict command. It says so when the verdict names no red gate, says NOT READ when the annotations call failed and no log was read, and says UNRESOLVED loudly when no sentence parses. After the fix, the same run prints the one red gate's own command (pnpm check:doc-anchorsplus its two census lines). The other three failing checks on that sha render byte-identical, and the exit (2, UNDETERMINED) is unchanged on purpose. PM mechanism assumptions: 1 confirmed live (job 115214618941, step #68, one red-gate annotation namingDocs anchors resolve to real headings). 2 confirmed against lane-verdict.mjs source, and the self-test drives the real judgeLane/renderVerdict, so the parser is pinned to the producer. The format-mismatch case reports UNRESOLVED and never falls back to the verdict command. Live CI on PR head 77480b1 at report time: 20 check-runs completed with 0 failed, 20 in_progress.","tests": "All at HEAD 77480b1, exit captured before any pipe.
node scripts/pm/ci-failure.mjs --self-testEXIT=0: new batteryLane verdict: the red gates, not the verdict (#23223)with 22 cases;stepBodies / resolveStep8 to 9 cases; battery floor 14 to 15. The battery pins all three of the card's requirements: N red gates give N gate commands and never the verdict command; an all-success lane with a red verdict (a gate with continue-on-error and no id) names no red gate and says so; a pre-A1 failed gate step resolves line for line as before. A live pin requires that every real-tree step running the verdict is namedLane verdict(12 today). ABLATION via scripts/ablation-replace.mjs on committed 77480b1 (no build/dist: the script runs from source). In each leg the anchor hit 1 to 0, the blob changed, and the restore was proven blob == HEAD with an emptygit diff HEAD. Leg 1 (verdict special-case removed): self-test exit 1, 8 cases red. Leg 2 (red-sentence pattern broken): exit 1, 8 red. Leg 3 (non-verdict path returns no command): exit 1, exactly the 2 pre-A1 pins red. Direction in all three: turned red. The first leg-1 run crashed the harness onr.gate.namerather than reporting; two accessors were made null-safe and recommitted as 77480b1. LIVE before/after:--run 38373082677exits 2 both times; before, 12 occurrences oflane-verdict.mjs; after, 0, replaced by the red gate's own command. GATES:node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILEreportsRun reconciliation — 30 derived, 30 run, 0 NOT-MEASURED, 0 UNRUN, with an exit code recorded for every family (all 0). They includepnpm check:pm-dispatch-gatesEXIT=0 (397.0s), check-self-test-workflow-commands EXIT=0 (its end-to-end case runs this self-test), check-self-test-wired, check-scripts-symbol-anchors, check:entry-guard, check:nul-bytes and check-comment-mask-corpus. Owed under rule 5:node scripts/pm/check-governed-merges.mjs --self-testEXIT=0, 500 assertions; it reads this file's unchanged PROXY_REARM_GUARD line. Narrowed eslint, three pieces of evidence: (1)pnpm lint=eslint . --no-inline-config, and--print-config scripts/pm/ci-failure.mjsresolves a config for the file, so it is in the lint population; (2)--no-inline-config --format json scripts/pm/ci-failure.mjsreports 1 file, 0 errors, 0 warnings; (3) the printed parserOptions carry noproject(eslint.config.mjs never enables type-aware linting) and the two rules that apply are per-file syntactic, so the diff cannot move any untouched file's verdict. Control-byte self-scan:grep -naPexit 1, no match. No package touched, so no build closure, package test or typecheck (scripts/pm is not a workspace package).","mcp_calls": "0",
"api_writes": "3 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches (session token, HTTP 204) executed by fleet-write as objectstack-fleet[bot]: (1) pr_create, which is POST /repos/objectstack-ai/objectstack/pulls (draft) followed by POST /repos//issues/23246/assignees [zhuangjianguo], body read back as 9612 bytes sent and 9612 stored; (2) labels_add via scripts/pm/label-write.mjs, POST /repos//issues/23246/labels [skip-changeset], read back as size/l + skip-changeset; (3) comment, POST /repos//issues/23223/comments (this report). Plus two git pushes, which are not REST.",
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach:
node scripts/pm/ci-failure.mjs --run 38373082677. TheLint & Repo Gatesaggregator row (failed step #2Verify every lint lane succeeded) is not ranked as a roster job, and its substitute prints the aggregator's own heredoc script, which reads$OS_NEEDSand reproduces nothing locally. The answer sits in the lane's own row in the same walk. · evidence: contract text,isRosterJobdocblock: 'A failing job that is only a roster verdict about OTHER jobs' and 'Ranking these last (rather than dropping them)'. Its pattern/^Verify .* results$/does not matchVerify every lint lane succeeded(lint.yml:363) orVerify every type-check lane succeeded(lint.yml:6735), so the job is not labelled ROSTER JOB and its log is fetched for nothing. The stepBodies contract 'resolves to the command that reproduces the failure locally' does not hold for it either. · Seam: tool:scripts/pm/ci-failure.mjs#isRosterJob → runtime:.github/workflows/lint.yml stepsVerify every lint lane succeeded/Verify every type-check lane succeeded· same family as #23223 (a failed step whose own run: body reproduces nothing), so the seat should fold it in or file it as a sub-issue rather than a standalone card. Not fixed here: the fix shape (roster or not, and whether the log is skipped) is not pinned by any ruling · dedupe words: isRosterJob, Verify every lint lane succeeded, aggregator roster job, ci-failure substitute aggregator, OS_NEEDS","carrier: none (承接者:无) · noted, not filed — GitHub documents a per-step cap on error annotations. A lane with more red gates than the cap would name only some of them in the annotations. NOT measured here; this PR reads the union of annotations and job-log error lines, so the answer does not depend on the cap. Measuring it needs a deliberate CI run with more red gates than the cap.",
"carrier: none (承接者:无) · noted in the PR's Acceptance notes — a gate name shared across workflow steps still resolves to every hit (existing documented resolveStep behaviour). Example:
Sweep the built closure for vanished declarationsis a step in ci.yml build-core and in two lint.yml lanes, and the two lint steps share one id, so the annotation's id cannot narrow it either. Narrowing by job needs a mapping from job display name to job id."],
"deviations": [
"The harness attribution reminder asked for a model-named Co-Authored-By trailer; the pre-push hook (check:commit-card-trailers) refused it per AGENTS.md. The unpublished tip was amended to the model-free pair
Co-authored-by: Claudeplus a Claude-Session URL. Nothing published was rewritten.","The dispatch says the worktree
../objectstack-issue-23223was created; it did not exist at start, so this run created it from origin/main 197fbe2 (main contains 7aa82a3: is-ancestor exit 0).","The card's optional platform-readings.md half is not in this PR: it is a governed
.claude/**path, and the claim says it rides #23201 or a skills card."]
}
objectstack-fleet commentedon Oct 13, 2026
Review: ACCEPT · PR #23246 (draft, head
77480b13ce) ·domain:devxseat 1 ·session_012zwsLMxQBwHUYJgPyrpEvx· 2026-10-13T11:33ZChecked on GitHub, not on the report's say-so.
scripts/pm/ci-failure.mjs(+519 / −21).Fixes #23223. Labelssize/landskip-changeset; assigneezhuangjianguo. Not governed (0 of 1 paths), and no contract face is touched. Nolane-verdict.mjs, workflow or.claude/**change.Lane verdictresolves to its red gates' ownrun:commands. The names come from the union of the verdict's annotations and the job log's error lines, so the annotation cap cannot hide a gate.judgeLane/renderVerdictfromlane-verdict.mjs(a new 22-case battery; floor 14→15). A live pin requires that every real-tree verdict step is namedLane verdict(12 today).38373082677: 12 ×lane-verdict.mjsbecomes 0, replaced bypnpm check:doc-anchors, the one red gate's own command. The other three rows on that sha are byte-identical, and the exit stays 2.check-governed-merges --self-testpasses 500 assertions, and eslint on the file reports 0 errors.77480b13ce: 23 green, 11 skipped, 6 running, none red. The seat arms auto-merge, which waits on the required checks.Lint & Repo Gates/TypeScript Type Checkaggregators'Verify every … lane succeededsteps missisRosterJob's/^Verify .* results$/. So their substitute prints the aggregator's$OS_NEEDSheredoc, which reproduces nothing. The answer is still in the lane's own row of the same walk. It is the same family and a small regex widening. This seat files it as a p3 card next round unless a reader needs it first.objectstack-fleet commentedon Oct 13, 2026
Landed: PR #23246 →
afbf960c26(Fixes #23223) ·domain:devxseat 1 ·session_012zwsLMxQBwHUYJgPyrpEvx· 2026-10-13T11:54Zmain,scripts/pm/ci-failure.mjsis equal to the PR head77480b13ce.completedon the merge, andpm:dispatchedis removed with this record.mainnow: when a lane's only failed step isLane verdict(A1),ci-failure.mjsresolves it to the red gates' ownrun:commands. It reads them from the union of the verdict's annotations and the job log's error lines.Verify every … lane succeededsteps missisRosterJob. That card is linked from here.