Skip to content

fix(pm): dispatch-gates' --changed --commands control accepts the no-diff refusal — main's push run has been red since #15105 - #15320

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-15278-changed-commands-control
Sep 4, 2026
Merged

fix(pm): dispatch-gates' --changed --commands control accepts the no-diff refusal — main's push run has been red since #15105#15320
baozhoutao merged 1 commit into
mainfrom
claude/issue-15278-changed-commands-control

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #15278

The self-test case that reds is not about a broken tool: it is about the tree the run stands
in. The card recorded it as a fresh-worktree annoyance and said "CI never sees this". That
premise is falsified — CI sees it on every push to main, and main's own commit status has
been red at Lint & Repo Gates step 31 PM dispatch-gates self-test since the control landed.

The shape

const changedCommandsRun = runCli(['--changed', '--commands']);
t(
  'CONTROL: --changed --commands is legal and answers, so the moved usage line describes a real combination',
  changedCommandsRun.status === 0 && (changedCommandsRun.stdout ?? '').length > 0,
);

The claim is about the flag COMBINATION — that --changed parses beside a stdout-shape flag
and REACHES the derivation. status === 0 is not that claim; it is a property of the caller's
tree. Where HEAD equals origin/main the derivation refuses by design, exit 2:

dispatch-gates: this branch changes nothing against 'origin/main' (merge base 3a4373f4c) — nothing to derive.

A push run of lint.yml has exactly that shape: the pushed head IS origin/main. A
pull_request or merge_group run does not — a PR branch and a queue branch both carry a diff
— which is why the queue kept merging green while the base branch's own status was red.

Measured — every completed push run of lint.yml on main

From GET /actions/workflows/lint.yml/runs?event=push&branch=main (cancelled runs = superseded
under concurrency; every run that completed is listed):

main push Lint & Repo Gates note
101ad2cc14428dd575 (09-03 19:41Z → 22:39Z) success ×4 before the control
369da918d (09-03 23:23Z, the commit that added the control) failure first red
35e94c96b, 97a22639b, f594e70d7, 2cc461030, fcc42e6c1, 1876d5dfd, ebb082204, 5c584231a, c351a84da, 919beca43, 50d6c924b, 9c1bcda38, 1bc3c092a, 460134af8, 4dd5041bd, 0e24b0c2c, fbecffe1d, 5b09356b7, 3a4373f4c (09-03 23:52Z → 09-04 09:57Z) failure ×19 every completed push run

Failing step confirmed by job read on three of them (3a4373f4c job 100985021378, 5b09356b7,
fbecffe1d): step 31, Process completed with exit code 1. The merge_group run of the same
3a4373f4c head (job 100979536132) is green.

Reproduced here on a checkout whose HEAD equals origin/main at 3a4373f4c — the push-to-main
shape — with exactly one red case out of 1381, this one.

What the case proves now

The predicate accepts either outcome that can only follow from reaching the derivation:

  • exit 0 with a command list on stdout, or
  • exit 2 with the no-diff refusal on stderr.

It matches that refusal on NO_DIFF_REFUSAL, a new constant the print site interpolates, rather
than on a retyped copy of the sentence — the pin follows the message instead of a memory of it.

A widened case is one reading away from accepting everything, so that reading is made
mechanically rather than left to the next author's eye. A positive control puts an ILLEGAL
combination through the SAME predicate and requires it to FAIL: --changed with a path, which
the argv chain refuses before any derivation runs, at the same exit 2 with a different sentence
(--changed derives the paths itself — do not pass paths with it.). No parse failure can
satisfy the corrected control.

The usage-line assertions above it are untouched, and no other case was weakened or removed.
The self-test grew by one case: 1381 → 1382.

Ablation — the positive control is load-bearing

Made the illegal combination legal (} else if (false && wantsChanged && argvPaths.length > 0) {),
which is the parse failure the corrected control must not accept:

  • mutation confirmed on disk before the run — target line present 1 → 0, injected line 0 → 1,
    blob 5ec8448acaf5175a39, and a probe of --changed --commands PATH that used to exit 2
    now exits 0 with 19 lines of commands;
  • self-test then reds exactly one case, the positive control:
    ✗ dispatch-gates self-test: 1 of 1382 case(s) failed.
  • restored via a trap on EXIT INT TERM using git checkout HEAD -- ABSOLUTE_PATH; restore
    proven by observation, not by an exit code — git diff HEAD empty, git status --porcelain
    empty, and the worktree blob back to the HEAD blob 5ec8448acca501ca75459d5b0a6d950e3b5231fd.

Verification (all at 209f5c596, under the shared verify lock)

run tree result
BEFORE, no diff (push-to-main shape at 3a4373f4c) HEAD == origin/main ✗ 1 of 1381 case(s) failed — the sole ✗ is this control
AFTER, branch (a) — tree HAS a diff pnpm check:pm-dispatch-gates ✓ 1382 cases pass, exit 0
AFTER, branch (b) — tree has NO diff, carrying this fix HEAD == origin/main == 209f5c596 ✓ 1382 cases pass, exit 0
ABLATION — illegal combination made legal tree HAS a diff ✗ 1 of 1382 case(s) failed — the positive control alone

Branch (b) was exercised in a separate clone whose own refs/remotes/origin/main was pointed at
this commit — the shared repository's refs were never touched.

Derived family re-derived at the final commit
(node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, 21 commands)
and run, plus check-self-test-workflow-commands.mjs and its --self-test, plus whole-repo
pnpm lint. All exit 0.

skip-changeset: scripts/pm/** only — nothing is published from a package by this diff.


🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

…diff refusal

The case asserts that `--changed` combines with a stdout-shape flag — that the
combination PARSES and REACHES the derivation. It demanded `status === 0`,
which is a property of the tree the run stands in and not of the combination:
where HEAD equals origin/main the derivation refuses by design with exit 2 and
the "changes nothing against ... nothing to derive" sentence, so the case went
red with nothing wrong with the tool. That shape is `main`'s own push run of
lint.yml, which has been red at the gate step on every push since the case
landed, while the merge_group run of the same head stayed green.

The predicate now accepts either outcome, matching the refusal on the constant
the print site interpolates rather than on a retyped copy of it, and a positive
control puts an ILLEGAL combination (`--changed` with a path — refused by argv
before any derivation, at the same exit 2 with a different sentence) through
the SAME predicate and requires it to fail, so widening the case cannot empty
it out.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dispatch-gates self-test: the --changed --commands CONTROL reds on any branch with no diff against origin/main — every fresh worktree's baseline run

2 participants