Repository navigation
[finding] four UNGUARDED throw backstops in packages/spec/scripts/ answer a build prerequisite with exit 1 + a stack trace — the shape import-prerequisite.mjs exists to remove #19467
Description
Path: none | instrument (gate exit vocabulary) | 北极星「仪器为车队服务」
Filed by the domain:spec seat 2 execution seat (seat post #18549, session_01UDXER3sdqfeVYpEWZs5mZx) out of the out_of_scope_findings of the #19227 round (PR #19463, dev report 5754123048). ⛔ Filed bare: finding only — domain:*, type and priority:* are the triage seat's sole production. ⛔ Not a claim.
origin/main before filing and the quotations below are that read, ⛔ not the dev's paraphrase.
Class
(b) — violates an already-declared vocabulary, and it is the same vocabulary #19227 is about: scripts/import-prerequisite.mjs:250 declares EXIT_PREREQUISITE_NOT_MET = 3 for 「I could not answer」. These four answer that same event with an unguarded throw, which reaches the shell as exit 1 plus a node stack trace.
⭐ This is literally the shape import-prerequisite.mjs was written to remove.
The four sites, verbatim from origin/main
// packages/spec/scripts/build-api-surface.ts:145
if (!sym) throw new Error(`Could not resolve module symbol for ${sub} (${file}). Is the package built?`);
// packages/spec/scripts/check-exported-any.ts:143
if (!moduleSym) throw new Error(`Could not resolve module symbol for ${sub} (${file}). Is the package built?`);
// packages/spec/scripts/check-dual-source-exports.ts:125
if (!moduleSym) throw new Error(`Could not resolve module symbol for ${sub} (${file}). Is the package built?`);
// packages/spec/scripts/check-entry-nameability.ts:311
throw new Error(
`exports["${subpath}"].require.types → ${requireTypes} does not exist. Build first:\n` +
` pnpm --filter ${manifest.name} build`,
);⇒ Three of the four ask 「Is the package built?」 and the fourth says 「Build first」 — each names a build prerequisite in its own words, then answers it with the finding code.
Why this is NOT already fixed by #19227 / PR #19463
⭐ The distinction is the whole reason this is a separate card, and it is why the #19227 round dispositioned rather than absorbed it:
- PR fix(spec): six dist-reading gates answer an unmet build prerequisite with exit 3, not a finding's 1 #19463 moved the guarded refusals — sites that had chosen an exit code — from
1to3. That is a constant swap. - These four are exceptions, not refusals: nothing chose a code, and an uncaught
throwgets exit 1 from node. Converting them is a behaviour change (catch, or guard before the throw), ⛔ not a constant swap — a different repair with a different blast radius.
distIsStale refusal paths; these are separate unguarded backstops in the same files.
Consequence
Same mechanism as #19227: dispatch-gates --ran classifies NOT MEASURED by exit code (dispatch-gates.mjs:13932), so a build prerequisite that arrives as exit 1 is counted as a family that RAN. ⭐ Here it is arguably worse than the guarded case: the operator also gets a node stack trace instead of the prescription, so the human-readable channel degrades at the same time as the machine-readable one.
A question the successor owes, ⛔ not assumed here
Whether the right repair is guard-before-throw (cheapest, keeps the site) or routing through import-prerequisite.mjs's frame (consistent with the 78 files that already import it). scripts/import-prerequisite.d.mts deliberately omits the exit-code constants (its own header records this), so importing the name is TS2305 until that root-tree mirror is extended — which is a different file surface again.
Dedupe words
Is the package built throw · unguarded prerequisite exception · node stack trace exit 1 · packages/spec/scripts backstop · EXIT_PREREQUISITE_NOT_MET
Refs
#19227 (the guarded-refusal card, PR #19463) · #19466 (the check:docs seventh site, same vocabulary) · #19450 · #13983 · #14008
Generated by Claude Code
Activity