docs(check-dts-emitted): name the silent worker-death class, not OOM - #13898
Conversation
The guard's header and its printed remedy both named OOM as the shape that leaves a build silently DTS-less. Measured, an OOM'd DTS worker is the LOUD path: Node delivers worker heap exhaustion as an 'error' event (ERR_WORKER_OUT_OF_MEMORY), and an 'error' event with no listener is rethrown by EventEmitter, so the run exits non-zero and prints a stack. The silent shape is a worker that ends without posting a message AND without an 'error' event, from a CJS caller. Reworded both places to lead with that class, to keep OOM present as the loud sibling a reader must be told it is NOT, and to replace the heap-headroom retry with a next action that discriminates between the two causes this guard can fire on. Text only: comments and one console.error string. The predicate, --self-test and rollout are untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
…s-emitted-oom-header
|
ACCEPT — What makes this one goodThe card asked for a header that names the class instead of the trigger. The delivered change does that and keeps OOM, relabelled as the loud sibling a reader must be told they are not looking at. ⭐ Deleting OOM would have been the obvious move and it would have been wrong: a debugger arriving from a memory-starved build needs to be told explicitly that they are in the other shape, and an absent paragraph tells them nothing. Verification — the load-bearing claims, re-derived by this seat
⭐ The On the remedy that was removedThe old text ended in Zone 2 — nothing falsified, and the measurement is whyThe dev reported no Zone 2 assumption falsified, but ⛔ did not take the card's premise on faith either: it built a harness with tsup's promise shape (only
Open question — answered, and already actionedThe dev found the wrong claim repeated outside the repo tree, in the body of closed issue #12167 ("OOM under memory pressure is the observed shape"), reported it, and ⛔ correctly did not widen the PR to act on it. ⇒ Option B, as recommended: a correcting comment is now posted on #12167, carrying the four-row measurement, and the body is left as filed. ⛔ Editing a closed card's body would rewrite the record of what was believed at the time; leaving it silent would keep a wrong sentence discoverable by search while the header that cites it says the opposite. The comment corrects the reader without falsifying the history. Governed-surface checkDiff is Arming
Generated by Claude Code |
Fixes #13509
Text-only rewrite of
scripts/check-dts-emitted.mjs: its header comment and the onemessage it prints to the engineer who trips it. The predicate, the
--self-testand therollout are untouched — see the mechanical zero-logic-change proof below.
Note on placeholders: the guard's real message spells the package placeholder with angle
brackets. It is written
PKGthroughout this body so the body sanitizer cannot eat it.The defect
The guard named OOM as the shape that leaves a build silently declaration-less, in two
places — the
THE DEFECT IT EXISTS TO CLOSEheader block, and the printed remedy, whichtold the engineer to retry with
NODE_OPTIONS=--max-old-space-size=8192.Measured, that is backwards, and the printed half is the expensive one: heap advice for a
failure that was never about heap cannot converge, so it sends the reader into a retry loop
while the genuinely useful step in the same message (
turbo --force) sits below it.A2.1 — the loud/silent claim, established here rather than taken on faith
The card's table is the whole basis of the rewrite, so I rebuilt it rather than citing it.
Harness mirroring tsup's promise structure exactly (a
Worker,postMessage, and onlyworker.on('message')registered — noerror, noexithandler), Node v22.22.2,linux x64 — the same platform the card reports:
process.exit()in worker, CJS callerexitonlyprocess.exit()in worker, ESM caller w/ top-level awaitexitonlyWarning: Detected unsettled top-level awaitresourceLimits: { maxOldGenerationSizeMb: 16 }errorError [ERR_WORKER_OUT_OF_MEMORY]+Unhandled 'error' event--max-old-space-size=64errorAll four rows reproduce. The mechanism is
EventEmitter: anerrorevent with no listeneris rethrown, so an OOM'd DTS worker fails loudly and non-zero — and a non-zero build is
never cached by turbo, which is exactly the property the header claimed OOM lacked.
In run 1 the harness's
Promise.all(...).then(() => console.log('BUILD REPORTED SUCCESS'))never fired and the process still exited 0 with zero bytes of output — the silent
false-success reproduced directly.
Nothing in Zone 2 was falsified. The card's measurement stands as written.
A2.2 — what the silent shape actually is
Not OOM. It is a worker that ends without posting a message and without emitting an
errorevent — a hardprocess.exitinside the worker, a terminated thread, any othermessage-less end. The caller's module system is load-bearing: the same message-less death
exits 13 with a warning under an ESM top-level
awaitand 0 in silence under CJS, andtsup's CLI is CJS, which is why the observed shape was silent.
Per the triage boundary, OOM is kept, not deleted — a reader who has just seen an OOM is
told explicitly that it is not this — and the rewrite does not assign a cause to the
original plugin-auth observation. It establishes only that whatever ended that thread did so
without an
errorevent, because the run exited 0.A2.3 — the new remedy, and why it discriminates
The heap-headroom retry is gone. Two discriminators replace it, both derivable from things
the engineer can already see:
1. Reaching the message is itself evidence. Every one of the 67 packages wires the guard
as
tsup && node ../../scripts/check-dts-emitted.mjs. The&&means the guard runs onlyif tsup exited 0 — and an OOM'd worker exits non-zero, so the build stops at
tsupand thisguard never runs. The message now says so: "Reaching this text is itself evidence the DTS
pass exited 0." That is what makes "more heap will not help" checkable rather than asserted.
2. One
lssplits the two real causes. The guard can fire for two quite differentreasons, and they need opposite responses:
The second branch is the one the old text could never reach: a manifest promising a path the
package's tsup entries never emit fails identically on every rebuild, so any retry-shaped
advice is wrong for it. The
turbo --forcecache-clearing step is kept, unchanged.A2.4 — census: is the wrong fact repeated?
Swept 942 files under
scripts/,content/docs/anddocs/by mechanism — comment markersstripped, newlines flattened (the sentence wraps), case-sensitive word-bounded
OOM, plus aco-occurrence pattern for OOM within 200 chars of a silence / exit-0 / never-settles claim.
Counter-checked against two phrases known to be present (
THE DEFECT IT EXISTS TO CLOSE,11907) — both fire, so the sweep is reading these files.Inside the repo tree,
scripts/check-dts-emitted.mjsis the only carrier. The otherOOMhits are all a different subject:docs/launch-readiness.md— unbounded in-memory adapter growth and retention caps. Unrelated.scripts/check-test-completeness.mjs— a vitest pool worker dying with "no JS error tocatch"; a different runtime and a narrower proposition (it does not claim process exit 0).
Its own defect (feat(spec): one canonical conformance table for the filter logical combinators #3812) has its own gate. Related in spirit, distinct in fact.
scripts/check-agent-test-spelling.mjs— a fixture string quoting the line above.check-type-check-coverage.mjs/check-test-typecheck.mts—max-old-space-sizefortsc, backed by a real captured
FATAL ERROR ... heap out of memorytrace. A genuinelyloud OOM; legitimate.
One repeat exists outside the tree: the body of #12167 (closed) carries the same
attribution verbatim — "OOM under memory pressure is the observed shape". It is an issue
body, not a repo file, and out of this card's fence. Reporting, not widening — say the
word and I will file it or correct it.
Verification
Final commit 5d42a06 (branch merged with
origin/mainbefore the sweep, so the gatederivation is not answering about a stale tree).
either a
//comment line or lies inside the singleconsole.error(...)argument —12 old-side lines (7 comment, 5 in the message), 53 new-side (35 comment, 18 in the
message), 0 outside both. No executable statement moved.
node scripts/check-dts-emitted.mjs --self-test→ exit 0, "all assertions passed."pnpm check:ratchet-remedy-authority→ exit 0. "OK check-ratchet-remedy-authority:179 scripts swept ... Control corpus: 26 hand-classified scripts, set-equality audited both
ways." This file's verdict stays
excludedbefore and after.this file: injected an offer-shaped remedy ("add an entry to the shrink-only baseline
in
scripts/dts-waiver-baseline.json") into the printed message, proved it on disk, andthe gate flipped to exit 1 /
UNMARKED: scripts/check-dts-emitted.mjs. Restored withgit checkout HEAD -- ABSOLUTE_PATHand verified byte-identical (blobd4a18319ebf8a0d3f553c48e8e45338060443787both sides), 0 residual occurrences.pnpm lint(eslint . --no-inline-config, full repo-wide, under the shared verifylock) → exit 0, 112s. No narrowing, so no narrowing argument is owed.
pnpm check:nul-bytes→ exit 0, 7614 files, no raw control bytes. Edited file is 0 bytesnon-ASCII.
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands) → 11 exit 0. The 12th,check-test-completeness.mjs, exits 3 =PREREQUISITE NOT MET: it grades a savedturbo run testlog and the family list invokes it with no argument. Its own text callsthis branch NOT MEASURED, "not a red, and there is nothing here to fix." Recorded as
not measured, not as green.
check:ratchet-remedy-authority— reproducingdispatch-gates places check:ratchet-remedy-authority in "undetermined" — a scripts/** sweep that no derived family names, so a full local family run misses it #13813 (its population is built at runtime from
readdirSync, so no path derivationreaches it). Run explicitly.
No changeset
Nothing publishes. The root package is
private: true, andscripts/sits outside everyglob in
pnpm-workspace.yaml, so this file ships in no package. The changed text is acontributor-facing build message and a maintainer-facing comment — no customer-visible
surface. Labelled
skip-changeset.Generated by Claude Code