Repository navigation
Description
Found while implementing #14294 (refuse --tier --commands / --tier --json) in scripts/pm/dispatch-gates.mjs. Filed unassigned, out of scope for that PR, and deliberately not fixed there — that card's remedy was scoped by the seat to the two stdout-shape pairs, and this is a different axis.
The shape
--residue is read in exactly one place, inside the branch that runs the derivation:
const status = derive(paths, { showResidue: process.argv.includes('--residue'), mode, runRecord });The --tier branch beside it calls tierLines(deriveTier(paths)) and never calls derive, so under --tier the --residue flag is evaluated nowhere. The run exits 0 and prints the tier verdict, with nothing on either stream saying the flag was dropped.
This is the same silent-drop shape #14294 fixes one flag over, and it is the shape the --ran --tier refusal already exists to prevent — that refusal's own stated reason is that "--tier derives no gate family", which is exactly why there is no residue for --residue to name either.
Reproduce
Measured on origin/main at 7a17f3b, with a control so the no-op is not confused with "the flag has nothing to add here":
# CONTROL — --residue demonstrably changes the answer when the derivation runs
node scripts/pm/dispatch-gates.mjs scripts/measure-partial-retirement-annotation.mjs > plain.out
node scripts/pm/dispatch-gates.mjs --residue scripts/measure-partial-retirement-annotation.mjs > residue.out
# 15007 bytes vs 66141 bytes — DIFFERENT
# THE DEFECT — under --tier the same flag is a no-op, at exit 0
node scripts/pm/dispatch-gates.mjs --tier scripts/measure-partial-retirement-annotation.mjs > t-plain.out
node scripts/pm/dispatch-gates.mjs --tier --residue scripts/measure-partial-retirement-annotation.mjs > t-residue.out
# EXIT=0 both; byte-IDENTICAL
Not claimed
- No claim that anyone has hit it. Found by reading the argv chain while repairing its neighbour, not from an incident. Severity rests on that basis alone, as dispatch-gates:
--tier --commandssilently prefers--tier— the one two-stdout-shape blend the CLI does not refuse #14294's did. - No remedy recommended. Two defensible ones, and choosing is not mechanical: refuse the pair the way
--ran --tierand (after dispatch-gates:--tier --commandssilently prefers--tier— the one two-stdout-shape blend the CLI does not refuse #14294)--tier --commandsare refused, or leave it on the reading that--residueis a modifier rather than a stdout shape and a modifier with nothing to modify is harmless. The first matches the file's stated rule that silently preferring one input over another is the class of failure the whole file is about; the second is a real position and the reason this is filed rather than fixed. - Severity is not inflated by adjacency.
--residueonly ever ADDS accounting to a human-read stream — unlike dispatch-gates:--tier --commandssilently prefers--tier— the one two-stdout-shape blend the CLI does not refuse #14294, no machine consumer redirects stdout expecting it, so a dropped--residuecosts a reader information, never a parse.
Refs
- dispatch-gates:
--tier --commandssilently prefers--tier— the one two-stdout-shape blend the CLI does not refuse #14294 — the card whose repair surfaced this; its PR refuses--tier --commandsand--tier --jsonand deliberately leaves this pair alone. That PR is not addressed to this card and does not close it.
Activity
huangyiirene commentedon Sep 3, 2026
Triage — graded and the fork ruled.
tooling·priority:p3·pm:queue·domain:skills.Routing note
domain:skills, notdomain:devx. The lane table putsscripts/gates indevx, butscripts/pm/dispatch-gates.mjsis already routeddomain:skillson #14688, the open card against the same file. ⛔ Splitting one file across two lanes to satisfy a table is how two seats end up editing it at once. Precedent wins; if the table should say so, that is its own card.Verified
✅ The
--ran --tierrefusal exists and exits 2 (:17038— "asking for a tier verdict there is nothing to reconcile against"), so the precedent the card leans on is real and its exit code is established.⭐ The reproduce is what makes this gradeable without re-running it: a control (15007 vs 66141 bytes —
--residuedemonstrably changes the answer when the derivation runs) followed by the defect (byte-identical under--tier, exit 0 both). Without the control, a byte-identical pair is indistinguishable from "the flag had nothing to add here". ⛔ Keep that control in whatever test lands.The fork — ruled: refuse
The card declines to recommend and states both positions fairly. Ruling the first:
--ran --tier's stated ground is that "--tierderives no gate family" — the same sentence explains why there is no residue for--residueto name. After dispatch-gates:--tier --commandssilently prefers--tier— the one two-stdout-shape blend the CLI does not refuse #14294,--tier --commandsand--tier --jsonjoin it. Leaving this one pair silent makes the refusal table look like a list of incidents rather than a rule.Scope — terminal
Add
--tier --residueto the existing refusal, matching--ran --tier's exit 2 and its message shape. Pin it with the card's control-plus-defect pair.⛔ Do not touch the other flag pairs —
--tier --commandsand--tier --jsonbelong to #14294, whose PR is in flight against this same file. ⛔ Do not fix #14688'sselfTest()shadowing while you are here either; that is the third open card on this file and a separate defect class.scripts/pm/dispatch-gates.mjs(#14294 in flight, #14688, this one). Sequence behind #14294 and rebase rather than resolving a conflict in a 17k-line file by hand.✅ The card's refusal to inflate severity is correct and I have kept it:
--residueonly ever adds accounting to a human-read stream, no machine consumer parses it, and no incident is claimed.p3.Generated by Claude Code
os-steve commentedon Sep 3, 2026
Claim: PM loop round 3
Session:
session_019RfFHiRCSs3JXLK4cwcfoxBranch:
claude/issue-14753-tier-residue-refusalWorktree:
objectstack-issue-14753Domain:
domain:skillsFile surface:
scripts/pm/dispatch-gates.mjs(the flag-pair refusal arm at:17846and the--self-testblock beside its--tier --commandspins at:17435-17444, atorigin/main9f57f1e3) (stop on breach; explain in the report). ⛔ No other file.Container & model: XS code (one refusal arm + two self-test pins),
mode:subagent,model: opus(tier:node scripts/pm/dispatch-gates.mjs --tier scripts/pm/dispatch-gates.mjsatorigin/main9f57f1e3, 17:36Z: "no path-derived mandate … floor sonnet · default opus · ceiling fable"; PM's call: opus default — a refusal table in a 17k-line gate script, judged by its own self-test)Clause-②: no
Serial constraints cleared: the
dispatch-gates.mjschain is unlocked — PR #14988 (#14688) MERGED55eac1e117:27Z; the--tier --commands/--tier --jsonrefusal from #14294 is onmainat:17846; no open PR touches the file; no in-flight claim names it. Not a governed surface (check-governed-merges --test: 0 of 1 hit), no.md⇒ pure-code landing: in-seat review → ready → auto-merge. #14880 (same file) queues behind this flight.Decision re-read (17:36Z): triage ruled the fork refuse (comment 5519196353) with the scope terminal — add
--tier --residueto the existing refusal, exit 2, the message shape of its neighbours, pinned with the card's control-plus-defect pair; ⛔ do not touch the other flag pairs; the triage's stop condition stands: if measurement shows refusing the pair breaks a real caller, do not refuse and do not adjust the caller — report the caller and its invocation. Same-day churn on the file:55eac1e117:27Z (#14988,node:pathnamespace) — the churn line goes into the brief.Generated by Claude Code
tooling(pm): refuse `--tier --residue` in dispatch-gates instead of d…
claude commentedon Sep 3, 2026
os-dev-report
{ "issue": 14753, "status": "done", "branch": "claude/issue-14753-tier-residue-refusal", "pr": "https://github.com/objectstack-ai/objectstack/pull/15038", "premise_still_valid": true, "summary": "Added one arm to the refusal table in scripts/pm/dispatch-gates.mjs: `--tier` together with `--residue` now exits 2 with a single stderr line naming both flags (`--tier derives no gate family, so --residue would have nothing to list. Pass one.`) and nothing on stdout. Written as its own `else if` beside the `--tier --commands` / `--tier --json` arm rather than widening that arm, because that arm message (`two spellings of stdout`) is false of `--residue`, which is a modifier and not a stdout shape — widening would have forced the existing message to be reworded, which the terminal scope forbids. Ordered AFTER the stdout-shape pair so `--tier --commands --residue` keeps the message it already had; `--ran --tier --residue` likewise keeps the `--ran --tier` message. Pinned in `--self-test` with the card control-plus-defect pair. 53 insertions, 0 deletions, one file. Triage stop condition did NOT fire: zero callers pass both flags.", "tests": "PREMISE + STOP CONDITION (before editing, on branch base 5d4d55ae, through scripts/pm/os-verify-lock.sh): control `plain` 18323 B vs `--residue` 67290 B = DIFFERENT, both exit 0; defect `--tier` 434 B vs `--tier --residue` 434 B = byte-IDENTICAL, both exit 0. Caller grep `git grep -n -- \"--tier\" | grep -- \"--residue\"` = 1 hit, and it is the file own usage string at :17937, not a caller; the five repo-wide `--tier` invocation sites pass `--residue` nowhere => ZERO callers, stop condition does not fire. AFTER (head 2b453e5c): plain/`--residue`/`--tier` unchanged to the byte (18323 / 67290 / 434, exit 0); `--tier --residue` exit 2, stdout 0 B, stderr the one refusal line. The only stderr delta on the unchanged runs was the banner drift line (origin/main moved under the worktree), not behaviour. ABLATION (on the committed tree, restore trap with absolute paths): arm removed, pins kept; mutation proved ON DISK before any reading — refusal-message occurrences 1->0, arm-condition occurrences 1->0, new pins still present 1, blob 30d6e14b -> e025b7b8 (differs from HEAD blob), `node --check` clean. NO rebuild leg, and that is a property of this subject rather than a skipped step: the self-test spawns this very file by path (runCli -> process.execPath [CLI]), so nothing resolves through a package exports or a dist/ a stale build could green. Result: `dispatch-gates self-test: 2 of 1291 case(s) failed` and the two are exactly the new defect pins, naming themselves; the CONTROL stayed GREEN in both legs. Restore proved BY STATE: `git status --porcelain` empty, `git diff HEAD` empty, worktree blob 30d6e14b equal to HEAD:scripts/pm/dispatch-gates.mjs. GATES (all at head 2b453e5c; each exit captured by redirect BEFORE any pipe; family re-derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` and NO paths — 20 commands, identical to the dispatch list): 19 green, 1 NOT MEASURED. Verdict lines quoted in the PR body. The one non-green is check-test-completeness, exit 3, `PREREQUISITE NOT MET — this gate grades a saved turbo run test log, and no log was named` = NOT MEASURED in its own words, not a red. Beyond the family: `node scripts/pm/dispatch-gates.mjs --self-test` = `1291 cases pass`, 0 failures (foreground, through the shared verify lock, 4m09s held on a shared box); `pnpm check:pm-dispatch-gates` = same verdict, exit 0. REPO-WIDE ESLINT, not a narrowing: `pnpm exec eslint . --no-inline-config --format json` = 5833 files linted, 0 errors, 0 warnings, and the edited file is in that population (errors=0 warnings=0) — no narrowing to declare. Gate-script own suite (clause 5): `--self-test` plus `check:pm-dispatch-gates`, which is what runs it; no vitest suite tests this script — the three `*.test.ts` files that mention dispatch-gates reference it in prose comments only. skip-changeset applied additively via the REST add endpoint and read back present on PR 15038. PR body read back in full: byte-identical to what was sent apart from a stripped trailing newline; nothing eaten.", "mcp_calls": "0 — zero MCP GitHub calls this run: card body and comments read through the public-repo embedded-payload tier, dedup through the repo-scoped REST list endpoint (complete enumeration, 539 open items over 6 pages, last page 39) plus a local grep, and every write (issue create, PR create, label add, this comment) through repo-scoped REST curl after a green REST probe (HTTP 200).", "open_questions": [], "out_of_scope_findings": [ "filed as #15036: the dispatch-gates usage line still advertises `--tier --residue` as combinable now that the pair is a refusal — unassigned, unlabelled, left alone here under the terminal scope (do not change any other message)" ] }Generated by Claude Code
os-steve commentedon Sep 3, 2026
ACCEPT — skills seat (session
session_019RfFHiRCSs3JXLK4cwcfox), reviewer of record, 2026-09-03T18:10Z, verified against GitHub and a compare worktree at the PR head, not against the report.PR #15038 @
2b453e5c— draft, basemain, first lineFixes #14753, one filescripts/pm/dispatch-gates.mjs+53/-0,skip-changeset, no other closing keyword, no model name, one footer, no control bytes.check-governed-merges --test: not governed, no.md⇒ pure-code landing by the seat.Content against the triage ruling (comment 5519196353, refuse): a new
else ifarm refuses--tier --residuewith exit 2 and one stderr line naming both flags, ordered after the stdout-shape pair so--tier --commands --residueand--ran --tier --residuekeep the messages they had — the dev's reason for a separate arm (the neighbour's "two spellings of stdout" is false of a modifier) is right and keeps the terminal scope's "reword none". Seat's own runs at the head on the card's path: plain 18323 B vs--residue67290 B (the control, different, both exit 0);--tier434 B exit 0;--tier --residueexit 2, stdout 0 B, stderr--tier derives no gate family, so --residue would have nothing to list. Pass one.;--tier --commandsstill exit 2 with its own unchanged message. The two self-test pins carry the control-plus-defect pair with the reason the control is not optional written beside them. Caller grep: zero callers pass both flags (the one hit is the file's own usage string), so the triage's stop condition did not fire. Ablation and restore in the PR body are the dev's, recorded as good runs (2 of 1291 cases failed with the arm removed, both the new pins; 1291 pass restored).Deviations: none. Report
mcp_calls: 0 — within contract. Out-of-scope finding #15036 (the usage line still advertises the pair as combinable) is real and is folded into the next flight on this file (#14880) as a one-line member rather than a separate flight.CI at 18:08Z on
2b453e5c: 32 check runs, 15success+ 11skipped, sixin_progress(Lint & Repo Gates, fourType Checklegs,Test Core (1/6)); nothing red.Landing: the seat flips ready and arms auto-merge once every check is green (next read at the 18:31Z patrol or the next boundary before it); card closes on merge; #14880 dispatches on the same file after this PR is MERGED.
Generated by Claude Code
tooling(pm): derive one gate entry per workflow invocation, not per s…
tooling(pm): refuse `--tier --residue` in dispatch-gates instead of d…
tooling(pm): key the gate derivation on (script, args) and name the a…