Measured while running the derived gate union for #11404 (PR #11554), in a fresh per-task worktree — the checkout shape CLAUDE.md mandates. Filed unassigned; not fixed there because it is unrelated to that card's scope.
What happens
A fresh worktree has no node_modules until pnpm install runs. Most gates in scripts/ are dependency-free by design and run fine. Two do not, and neither says so:
$ node scripts/check-ci-filter-parity.mjs --self-test
node:internal/modules/package_json_reader:314
throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'yaml' imported from
/home/user/objectstack-issue-11404/scripts/check-ci-filter-parity.mjs
... 7 more frames of node internals ...
exit 1
$ node scripts/ts-parse.mjs --self-test
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'typescript' imported from
/home/user/objectstack-issue-11404/scripts/ts-parse.mjs
exit 1
Both go green immediately after pnpm install, unchanged — measured on the same tree, same commit: check-ci-filter-parity --self-test 39 assertions pass, ts-parse --self-test 28 cases pass.
Why it is worth a card
check-ci-filter-parity is a derived family: dispatch-gates.mjs names it for any card touching scripts/**, so a dev following the standard workflow meets it on their first run in the new worktree. What they get is a node-internals stack trace naming a package, with nothing linking it to a missing install and nothing saying whether the gate's verdict is unknown or negative.
The repo already has the idiom and states the rule this violates. check-i18n-coverage.mjs's reportPrerequisiteNotMet prints the condition, the command that satisfies it, and — the load-bearing half — that nothing was measured:
Nothing was measured: no config was linted and no count was compared, so this result says NOTHING about whether any declared label went untranslated.
A raw throw carries none of that. Exit 1 from an unmet prerequisite and exit 1 from a real finding are the same reading, and the failure direction is the expensive one: a dev who assumes "this needs an install" and moves on has recorded a gate as run when it never executed a single assertion. scripts/pm/dispatch-gates.mjs's own CHANGE_KIND_GATES prose makes the same point about the ratchet line — "an unexplained throw reads as 'not applicable to me' — which is a green report over a gate that never ran."
Not asserted
No claim about scope beyond the two measured. Not swept: whether other scripts/** gates import a bare specifier and would behave the same — worth a companion sweep, and it is mechanical (a bare specifier in a scripts/ gate is exactly what firstPartyImportTargets already refuses to follow, so the list is derivable rather than hand-collected).
Also not asserted: whether the remedy is a per-gate preflight in the reportPrerequisiteNotMet idiom, or one shared preflight the two call. The second is smaller if the sweep finds more than two.
Related
#11404 / PR #11554 (where this was measured) · reportPrerequisiteNotMet in scripts/check-i18n-coverage.mjs (the idiom) · checkCliBuildPrerequisite in the same file (the same shape for a build prerequisite)
Generated by Claude Code
Measured while running the derived gate union for #11404 (PR #11554), in a fresh per-task worktree — the checkout shape
CLAUDE.mdmandates. Filed unassigned; not fixed there because it is unrelated to that card's scope.What happens
A fresh worktree has no
node_modulesuntilpnpm installruns. Most gates inscripts/are dependency-free by design and run fine. Two do not, and neither says so:Both go green immediately after
pnpm install, unchanged — measured on the same tree, same commit:check-ci-filter-parity --self-test39 assertions pass,ts-parse --self-test28 cases pass.Why it is worth a card
check-ci-filter-parityis a derived family:dispatch-gates.mjsnames it for any card touchingscripts/**, so a dev following the standard workflow meets it on their first run in the new worktree. What they get is a node-internals stack trace naming a package, with nothing linking it to a missing install and nothing saying whether the gate's verdict is unknown or negative.The repo already has the idiom and states the rule this violates.
check-i18n-coverage.mjs'sreportPrerequisiteNotMetprints the condition, the command that satisfies it, and — the load-bearing half — that nothing was measured:A raw throw carries none of that. Exit 1 from an unmet prerequisite and exit 1 from a real finding are the same reading, and the failure direction is the expensive one: a dev who assumes "this needs an install" and moves on has recorded a gate as run when it never executed a single assertion.
scripts/pm/dispatch-gates.mjs's ownCHANGE_KIND_GATESprose makes the same point about the ratchet line — "an unexplained throw reads as 'not applicable to me' — which is a green report over a gate that never ran."Not asserted
No claim about scope beyond the two measured. Not swept: whether other
scripts/**gates import a bare specifier and would behave the same — worth a companion sweep, and it is mechanical (a bare specifier in ascripts/gate is exactly whatfirstPartyImportTargetsalready refuses to follow, so the list is derivable rather than hand-collected).Also not asserted: whether the remedy is a per-gate preflight in the
reportPrerequisiteNotMetidiom, or one shared preflight the two call. The second is smaller if the sweep finds more than two.Related
#11404 / PR #11554 (where this was measured) ·
reportPrerequisiteNotMetinscripts/check-i18n-coverage.mjs(the idiom) ·checkCliBuildPrerequisitein the same file (the same shape for a build prerequisite)Generated by Claude Code