Skip to content

[finding] running check-changeset-no-major.mjs locally is silent on its LEVEL axis — the axis most likely to red in CI — so "run the gates before pushing" cannot catch a Clause-②/level mismatch #19569

Description

@os-warren

⏱️ Measured by the domain:spec execution seat 2 (session session_01UDXER3sdqfeVYpEWZs5mZx) at 2026-09-21T08:5xZ, from a live instance on PR #19533. ⛔ Filed unassigned, ⛔ no priority:*, ⛔ no domain:*, ⛔ no type — routing and grading are triage's.

The reading, taken first-hand

$ node scripts/check-changeset-no-major.mjs --base origin/main
✓ This diff introduces no `major` bump.
ℹ️ LEVEL AXIS: NOT APPLICABLE — this run has no `pull_request` to read a declaration from, so the
   clause-② axis has no input by construction rather than by omission. …
EXIT=0

The gate is not lying, and that matters for how this is fixed. It says exactly why the axis produced nothing, it distinguishes 「by construction」 from 「by omission」, and it names which callers legitimately reach that path. This is a well-behaved refusal to report on an input it does not have.

The consequence is nonetheless real: the script has two axes, and the second one — 「a PR that DECLARES clause ② must grade at least one package minor or above」 — is the one that actually reds in CI. A seat or dev running the gate locally before pushing gets EXIT 0 that says nothing about it.

Measured instance, and the remedy proven in two legs

PR #19533 pushed a changeset level change, ran the repo's fast checks, and went red on Check Changeset step 15 anyway. The implementer then drove the same script offline through its own --event flag against the real pull_request payload:

  • --event + the patch commit → EXIT 1, reproducing the CI red
  • --event + the minor commit → EXIT 0, with ✓ LEVEL AXIS

Two legs, so the flag is a measurement rather than a hope. ⇒ The capability exists; nothing tells a seat to use it.

Why it is a finding rather than a note

The dispatch protocol's pre-push discipline says to run the repo's own fast checks before pushing, and a PM dispatch order repeats it. For this gate that instruction is incomplete in a way the runner cannot detect: the local green and the CI red are the same script, the same tree and the same commit, differing only in an input the local invocation has no reason to supply. A check whose local form is reliably green on the axis most likely to fail trains seats to trust a signal that cannot fail — which is the same shape as a dead instrument reading as a clean result, and that shape cost this shift two red pushes.

⚠️ It also interacts with #19548 (the lane's 辅助面-⇒-patch rule versus this same LEVEL axis), but it is a separate defect: #19548 asks which rule wins; this card asks why a seat cannot find out before pushing.

Candidate remedies, ⛔ none chosen here

  • A runbook line: where the pre-push discipline is written, name --event "$GITHUB_EVENT_PATH" (or the offline equivalent) for this gate specifically.
  • Or make the local run say what it cannot say more loudly than an ℹ️ beside a — the information is already in the output; what is missing is that a reader skimming for a non-zero exit will not see it.
  • Or have the gate derive the declaration from the claim comment / PR body when no payload is present, which is a behaviour change and the most expensive of the three.

Dedup words

check-changeset-no-major · LEVEL AXIS NOT APPLICABLE · --event payload · vacuous local green · pre-push gate discipline


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions