Skip to content

Report the tsup DTS-worker silent death upstream — a worker.on('error') / worker.on('exit') pair would end the class at its source #12167

Description

@yinlianghui

Filed out of #12078 (the rollout of scripts/check-dts-emitted.mjs across every tsup-built package), on the dispatching PM's ruling that patching a third-party dependency is a maintainer decision and not an implementer's. Nothing here is a patch; this card asks for a decision to report the defect upstream, and carries the reproduction so the report can be written from it.

The defect, in tsup's own code

tsup 8.5.1 runs DTS generation in a worker_threads.Worker (node_modules/tsup/dist/index.js, the dtsTask inside Promise.all([dtsTask(), mainTasks()])) and settles the build promise only from that worker's message events:

await new Promise((resolve, reject) => {
  const worker = new Worker(path.join(__dirname, './rollup.js'));
  worker.postMessage({ ... });
  worker.on('message', (data) => {
    if (data === 'error') { terminateWorker(); reject(...); }
    else if (data === 'success') { terminateWorker(); resolve(); }
    ...
  });
  // no worker.on('error'), no worker.on('exit')
});

There is no error handler and no exit handler. A worker that dies without posting a message — OOM under memory pressure is the observed shape — settles neither branch: the promise never settles, the event loop drains, and node exits 0 with the esbuild pass having already written dist/. The run leaves a dist/ holding index.js / index.mjs / maps and zero .d.ts, and reports success.

Why it matters to us specifically

turbo caches only successful tasks. Exit 0 on a run with OS_SKIP_DTS unset hashes as — because it is — an ordinary full build, so the declaration-less dist/** is cached under the ordinary hash and replayed on every later run; a plain rebuild is a cache HIT that restores it, and only --force replaces the entry. The symptom lands on someone else's diff, as TS7016: Could not find a declaration file for module '...'.

Reproduced end to end on @objectstack/plugin-auth in #11907 / PR #12076: build exit 0, turbo 26 successful, 26 total, dist/ with zero declarations, cached under the same hash a healthy build produces.

The upstream fix being proposed for report

Register both missing handlers on the worker, so a death that posts no message rejects the build promise instead of leaving it pending:

  • worker.on('error', reject) — the worker threw or failed to start.
  • worker.on('exit', code) — reject when the promise has not already settled, whatever the code, since a clean exit without a success message is still a DTS pass that produced nothing.

Either one alone closes the observed OOM shape; the pair closes the class.

What we already have, and why this is a complement

PR #12076 landed scripts/check-dts-emitted.mjs (asserts every declaration path the manifest promises is present and non-empty, no-op under OS_SKIP_DTS), and #12078 rolls it into all 67 tsup-built packages' build scripts. That protects this repo now, on our schedule. An upstream fix ends the class for everyone and lets the guard become redundancy rather than the only barrier. They are complements, not alternatives — this card does not propose removing the guard.

Decision asked for

Whether to open the upstream report (tsup issue / PR), and by whom. ⛔ Note what is explicitly not proposed here: carrying a patched tsup in this repo. A patched third-party dependency is a standing supply-chain and maintenance obligation, and that call belongs to the maintainer.

Activity

  1. os-steve commented on Aug 26, 2026

    @os-steve
    Collaborator

    Maintainer ruling recorded — A: open the upstream report; a seat drafts, the maintainer reviews and submits

    Provenance: maintainer, 2026-08-26, live PM chat (decision-inbox batch 3, session session_01JANH3y7qe3MD8aLaLXci8N), verbatim: 「其他接受你的建议」 — accepting the presented recommendation A.

    State: needs-user-decision → pm:queue (domain:devx stands — drafting is a small, mechanical dispatch).


    Generated by Claude Code

  2. os-project-manager commented on Aug 30, 2026

    @os-project-manager
    Collaborator

    Draft upstream report for tsup — awaiting maintainer review and submission

    Per the ruling on this card (A: a seat drafts, the maintainer submits under their own name), below is the complete, copy-pasteable text for the upstream tsup issue. Nothing has been posted to any repository outside this one. No ObjectStack issue numbers, links or jargon appear in the draft.

    Verification done before drafting (all measured today, not recalled)

    Check Result
    tsup version this repo resolves pnpm-lock.yaml on origin/main: tsup@8.5.1 (root package.json: ^8.5.1)
    Latest tsup on npm 8.5.1 — dist-tags.latest, published 2025-11-12. The pin is already the newest release
    Installed tsup/dist/index.js worker.on("message", ...) only — no error, no exit handler. Defect present
    Upstream egoist/tsup@main, src/index.ts (fetched raw today) same shape, lines ~214-254 — not fixed upstream
    End-to-end repro on a clean standalone tsup 8.5.1 install exit 0, empty stderr, dist/ with index.js and zero .d.ts
    Proposed two-line patch applied to that install killed-worker build now exit 1; healthy build still exit 0 with declarations

    Premise stands. The defect reproduces on the version this repo pins, which is also the newest published version and matches upstream main.

    One correction to this card's text, carried into the draft

    The card (and the header comment in the in-repo guard) names OOM as the observed shape of the silent death. Measured on Node v22.22.2, that attribution is wrong, in the direction that matters:

    • A worker that exits without posting a message (thread called process.exit, was terminated, died message-less) fires only 'exit'. Nothing listens. Promise pending, event loop drains, exit 0, silent. This is the silent-success defect.
    • A worker that runs out of memory fires 'error' with ERR_WORKER_OUT_OF_MEMORY. With no 'error' listener, Node's EventEmitter rethrows it, so the process dies loudly: Unhandled 'error' event, exit 1. Measured both with per-worker resourceLimits and with a process-wide --max-old-space-size.

    So OOM does not produce the exit-0 shape on Node 22. Had the draft asserted it, the upstream maintainer would have run it, seen a crash, and closed the report as unreproducible. The draft therefore reports the class — any message-less worker death — with a deterministic repro that does not depend on memory pressure, and reports the unhandled-'error' path as the second, separate consequence of the same missing pair.

    Both are still fixed by exactly the two lines this card proposes.

    Duplicate check

    One targeted search of egoist/tsup issues found no report of this silent-success behaviour. The nearest neighbours are OOM crash reports (egoist/tsup#920 ERR_WORKER_OUT_OF_MEMORY, egoist/tsup#1325 dts OOM) — the loud path, a different symptom. The draft references them as related, not duplicates. The search was semantic, not exhaustive; a quick tracker search before submitting is still worth the maintainer's minute.


    BEGIN upstream text (copy from here)

    Title: dts build: a worker that dies without posting a message leaves the build promise pending — tsup exits 0 having emitted no declarations

    Body:

    Summary

    In dtsTask (src/index.ts), the DTS worker's promise is settled only from worker.on('message', ...). There is no 'error' handler and no 'exit' handler.

    If the worker thread ends without posting a message, neither branch ever runs. The promise stays pending forever, Promise.all([dtsTask(), mainTasks()]) never settles, nothing throws, nothing prints — the event loop simply drains and tsup exits 0. The esbuild pass has already written dist/ by then, so the run leaves a dist/ containing index.js and zero .d.ts files, and reports success.

    Silent success is materially worse than a crash here. Build caches (turbo, nx, CI artifact caches) key on exit status, so the declaration-less dist/ is cached under the same hash a healthy build produces, and replayed on every later run — a plain rebuild is a cache hit that restores the bad artifact. The failure then surfaces somewhere else entirely, in an unrelated consumer, as TS7016: Could not find a declaration file for module '...', which reads as that consumer's fault.

    Reproduction

    Deterministic, about 30 seconds, no memory pressure needed:

    mkdir tsup-dts-worker-repro && cd tsup-dts-worker-repro
    npm init -y >/dev/null
    npm i -D tsup@8.5.1 typescript@5.9.3
    
    mkdir src
    printf 'export interface Greeting { readonly to: string }\nexport const greet = (g: Greeting): string => `hello ${g.to}`;\n' > src/index.ts
    printf '{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", "strict": true, "declaration": true, "skipLibCheck": true } }\n' > tsconfig.json
    
    # A preload that makes the DTS worker die without posting a message.
    # --require is inherited by worker threads through execArgv, so this runs
    # inside the DTS worker; the main thread is left untouched.
    printf "const { isMainThread } = require('node:worker_threads');\nif (!isMainThread) process.exit(0);\n" > kill-dts-worker.cjs
    
    # 1) healthy build, for comparison
    npx tsup src/index.ts --dts --format esm; echo "exit=$?"; ls dist
    
    # 2) identical build, DTS worker dies message-less
    rm -rf dist
    node --require ./kill-dts-worker.cjs ./node_modules/tsup/dist/cli-default.js \
      src/index.ts --dts --format esm; echo "exit=$?"; ls dist

    The preload stands in for any real cause of a message-less worker death — a process.exit() reached anywhere inside the worker's dependency graph, a terminated thread, a thread killed without an error event. The bug is not in what killed the worker; it is that tsup has no handler for the worker ending at all.

    Observed

    Run 1 (healthy):

    ESM dist/index.js     72.00 B
    ESM ⚡️ Build success in 28ms
    DTS Build start
    DTS ⚡️ Build success in 1185ms
    DTS dist/index.d.ts   128.00 B
    exit=0
    dist: index.d.ts  index.js
    

    Run 2 (worker dies message-less):

    CLI Building entry: src/index.ts
    CLI Using tsconfig: tsconfig.json
    CLI tsup v8.5.1
    CLI Target: es2022
    ESM Build start
    ESM dist/index.js     72.00 B
    ESM ⚡️ Build success in 36ms
    exit=0
    dist: index.js
    

    Output simply stops. DTS Build start never prints, stderr is empty (0 bytes), the exit code is 0, and dist/ has no declarations.

    Expected

    A DTS pass that produced nothing should fail the build: non-zero exit with a message naming the DTS worker.

    Cause

    src/index.ts, inside dtsTask (the worker block is around lines 214-254 on main today):

    const worker = new Worker(path.join(__dirname, './rollup.js'))
    
    const terminateWorker = () => {
      if (options.watch) return
      worker.terminate()
    }
    
    worker.postMessage({ /* ... */ })
    
    worker.on('message', (data) => {
      if (data === 'error') {
        terminateWorker()
        reject(new Error('error occurred in dts build'))
      } else if (data === 'success') {
        terminateWorker()
        resolve()
      } else {
        /* log forwarding */
      }
    })
    // no worker.on('error'), no worker.on('exit')

    Two Node-level worker outcomes are unhandled, and they fail differently:

    1. The worker exits without posting a message. Only 'exit' fires; nothing listens; the promise stays pending. Measured: exit 0, no output at all — the silent case above.
    2. The worker emits 'error' (it threw, failed to start, or hit ERR_WORKER_OUT_OF_MEMORY). With no 'error' listener, EventEmitter rethrows on the main thread, so the process dies with Unhandled 'error' event and an internal Node stack rather than tsup's own build-failure path, and terminateWorker() never runs. Measured on Node v22.22.2:
    node:events:497
          throw er; // Unhandled 'error' event
          ^
    Error [ERR_WORKER_OUT_OF_MEMORY]: Worker terminated due to reaching memory limit: JS heap out of memory
        at [kOnExit] (node:internal/worker:316:26)
    

    Case 2 at least exits non-zero; case 1 is the one that poisons build caches.

    Suggested fix

    Register the two missing handlers on the worker:

       worker.on('message', (data) => {
         // ...unchanged...
       })
    +  worker.on('error', reject)
    +  worker.on('exit', (code) => {
    +    reject(
    +      new Error(`dts build worker exited with code ${code} without reporting a result`),
    +    )
    +  })

    Either handler alone closes the observed case; the pair closes the class.

    On the success path terminateWorker() triggers 'exit', but rejecting an already-resolved promise is a no-op, so no settled flag is strictly required — verified below. A boolean guard works equally well if you prefer it explicit.

    Verification of the patch

    Applied exactly the diff above to the installed tsup/dist/index.js (8.5.1) and re-ran both builds:

    Run Before After
    Worker dies message-less exit 0, silent, no .d.ts exit 1, Error: dts build worker exited with code 0 without reporting a result
    Healthy build exit 0, dist/index.d.ts emitted exit 0, dist/index.d.ts emitted (unchanged)
    Worker OOM exit 1, Unhandled 'error' event, Node internal stack exit 1, rejected through tsup's own path: Worker terminated due to reaching memory limit

    Environment

    • tsup 8.5.1 (latest on npm; dist/index.js and src/index.ts on main both carry the shape above)
    • Node v22.22.2, Linux x64 (Ubuntu 24.04)
    • npm, clean standalone install

    Related, not duplicates

    egoist/tsup#920 and egoist/tsup#1325 report the DTS worker running out of memory — the loud path (case 2). This report is the silent path (case 1): exit 0, declarations missing, no diagnostic at all. The same two handlers improve case 2's diagnostic as a side effect.

    I am happy to open a PR with the patch if that is useful.

    END upstream text


    Generated by Claude Code

  3. os-project-manager commented on Aug 30, 2026

    @os-project-manager
    Collaborator

    os-dev-report

    {
      "issue": 12167,
      "status": "done",
      "branch": "none - drafting task, no code change, no worktree created",
      "pr": null,
      "premise_still_valid": true,
      "summary": "Drafting task per the maintainer's ruling A (2026-08-26). Re-derived the reproduction against the tsup this repo actually pins today and found the premise intact: origin/main resolves tsup@8.5.1, which is also npm's dist-tags.latest (published 2025-11-12), and egoist/tsup@main src/index.ts still registers only worker.on('message') with no error and no exit handler. Built a deterministic end-to-end reproduction on a clean standalone tsup 8.5.1 install (a --require preload that exits the DTS worker thread, since --require is inherited by workers via execArgv): the build exits 0 with an empty stderr and a dist/ holding index.js and zero .d.ts. Applied the proposed two-line patch to that install and confirmed it converts the run to exit 1 while leaving the healthy build unchanged. Posted the complete upstream issue text as a comment on this card for the maintainer to submit under their own name; nothing was posted to any repository outside this one, and option C was not proposed. One correction to the card is carried into the draft and filed separately: OOM is NOT the silent shape - an OOM'd worker emits ERR_WORKER_OUT_OF_MEMORY as an 'error' event which, unhandled, exits 1 loudly. Asserting OOM upstream would have handed the tsup maintainer an unreproducible report.",
      "tests": "No repo build/test needed - zero files changed in any ObjectStack repo (git status --porcelain on the shared checkout: clean; git worktree list: no new worktree). Verification was of the third-party defect, all measured today on Node v22.22.2 / Linux x64 / Ubuntu 24.04. (1) Version derivation: `git show origin/main:pnpm-lock.yaml | grep tsup@` -> `tsup@8.5.1`; root package.json `\"tsup\": \"^8.5.1\"`; npm registry `dist-tags.latest` = 8.5.1, so the pin is already newest. (2) Defect present in the installed bundle: `worker.on(\"message\"` = 1 hit, `worker.on(\"error\"` / `worker.on(\"exit\"` = 0 hits in tsup/dist/index.js. (3) Not fixed upstream: raw fetch of egoist/tsup@main src/index.ts (http=200) shows the same block, lines ~214-254. (4) Real reproduction, clean standalone install of tsup@8.5.1 + typescript@5.9.3: baseline build exit=0 with dist/index.d.ts emitted; identical build with a preload that runs process.exit(0) when !isMainThread gives exit=0, stdout 177 bytes ending at 'ESM Build success', stderr 0 bytes, dist/ = index.js only, d.ts count 0. (5) Patch verification, mutation confirmed on disk before each run by grep counts (error-handler 1, exit-handler 1, message-handler 1 after the edit; 0/0 after restore, and `cmp -s` byte-identical to the pre-mutation copy): patched + killed worker -> exit=1, `Error: dts build worker exited with code 0 without reporting a result`; patched + healthy -> exit=0 with dist/index.d.ts, confirming the terminate-triggered 'exit' reject is a post-settle no-op. (6) Death-mode table behind the correction: message-less exit from CJS -> exit 0 silent; message-less exit under ESM top-level await -> exit 13 with an unsettled-TLA warning; worker OOM with resourceLimits 16MB -> exit 1 `Unhandled 'error' event` ERR_WORKER_OUT_OF_MEMORY; worker OOM under process-wide --max-old-space-size=64 -> exit 1, same shape; OOM with both handlers installed -> clean rejection, `BUILD FAILED: Worker terminated due to reaching memory limit`. Draft comment read back from the API after posting: 10017 bytes, byte-identical to the local source apart from the platform collapsing one blank line before the footer - nothing eaten by the sanitizer.",
      "mcp_calls": "6 - issue_read get, issue_read get_comments, search_issues (egoist/tsup dedupe), add_issue_comment (the draft), search_issues (in-repo dedupe before filing), issue_write create, issue_write update. Repo-scoped REST GET works for this seat (comment read-back returned 200) but the search API returns 403, so both dedupe searches went through MCP.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #13509 (finding): scripts/check-dts-emitted.mjs names OOM as the observed silent-build shape in both its header comment and the failure message it prints, and its printed remediation leads with a --max-old-space-size retry - measurement shows an OOM'd worker is the loud exit-1 path, so the advice targets a mode that cannot produce this failure. Docs/message only; the guard's logic is correct and untouched."
      ]
    }

    Delivery note: the drafted upstream text is the preceding comment, marked BEGIN/END for copy-paste. No upstream issue was opened - the external signature is the maintainer's.


    Generated by Claude Code

  4. os-project-manager commented on Aug 30, 2026

    @os-project-manager
    Collaborator

    ✅ ACCEPT · R32 · domain:devx PM seat — report-only, ⛔ no PR, zero files changed

    Drafting task per the maintainer's ruling A. The draft is posted above for review and submission under the maintainer's own name; ⛔ nothing was posted outside this repository, and ⛔ option C was not proposed.

    ⭐⭐⭐ The re-derivation caught a false cause that would have gone upstream

    I made "re-derive against the tsup this repo pins today" a precondition because the card is several rounds old. It paid, and not in the way I expected. The premise held — but one of the card's stated facts is wrong:

    ⛔ OOM is NOT the silent shape. An OOM'd worker emits ERR_WORKER_OUT_OF_MEMORY as an 'error' event, which — unhandled — exits 1 loudly.

    ⇒ "Asserting OOM upstream would have handed the tsup maintainer an unreproducible report."

    ⭐ Sit with what that avoided. This deliverable goes out under the maintainer's name, to a third-party project. A report whose stated cause cannot produce the described symptom gets closed as invalid, and the cost is not a CI cycle — it is credibility with an upstream maintainer, spent on our behalf, unrecoverably.

    The death-mode table that establishes it, all measured today on Node v22.22.2:

    mode result
    message-less exit from CJS exit 0, silent ← the real shape
    message-less exit under ESM top-level await exit 13, unsettled-TLA warning
    worker OOM, resourceLimits 16MB exit 1, Unhandled 'error' event ERR_WORKER_OUT_OF_MEMORY
    worker OOM, process-wide --max-old-space-size=64 exit 1, same shape
    OOM with both handlers installed clean rejection

    The premise itself, verified rather than assumed

    • origin/main resolves tsup@8.5.1, which is also npm's dist-tags.latest (published 2025-11-12) ⇒ ⭐ the pin is already newest, so "upgrade first" is not an available answer.
    • The defect is present in the installed bundle: worker.on("message" = 1 hit, worker.on("error" / worker.on("exit" = 0 hits.
    • Not fixed upstream: a raw fetch of egoist/tsup@main src/index.ts (http 200) shows the same block at ~214-254.

    ⭐⭐ The patch was verified before being proposed, not merely described

    A clean standalone tsup@8.5.1 + typescript@5.9.3 install, with a deterministic reproduction — a --require preload that exits the DTS worker thread, exploiting that --require is inherited by workers via execArgv. Baseline emits dist/index.d.ts; the killed-worker run gives exit 0, stderr 0 bytes, dist/ = index.js only, d.ts count 0.

    Then the proposed two-line patch applied to that install: killed worker → exit 1 with Error: dts build worker exited with code 0 without reporting a result; healthy build → exit 0 with index.d.ts still emitted, confirming the terminate-triggered 'exit' reject is a post-settle no-op.

    ⭐ That last check is the one an upstream maintainer will actually ask about — "does your fix break the happy path?" — and it is answered before they ask. Mutation confirmed on disk by grep counts before each run, restore proven byte-identical with cmp -s.

    Filed

    #13509 — scripts/check-dts-emitted.mjs names OOM as the observed silent-build shape in both its header comment and its printed failure message, and its remediation leads with a --max-old-space-size retry. Measurement says an OOM'd worker is the loud exit-1 path ⇒ ⛔ the advice targets a mode that cannot produce this failure.

    ⚠️ Note what that is: a guard whose logic is correct telling an operator to do something that cannot help, for a failure mode that is not the one occurring. The guard's logic is untouched and correct — only its prose lies. ⛔ Ungraded, unassigned; triage's to route.

    Hygiene

    Zero files changed in any ObjectStack repo — shared checkout git status --porcelain clean, git worktree list shows no new worktree. Draft read back from the API after posting: 10017 bytes, byte-identical apart from the platform collapsing one blank line before the footer.

    ⛔ This card is not closed by this comment — the deliverable is a draft awaiting the maintainer's review and submission. It closes when that submission happens, or when the maintainer decides not to send it.


    Generated by Claude Code

  5. claude commented on Aug 31, 2026

    @claude
    Contributor

    State correction · domain:devx PM seat — pm:dispatched ⇒ pm:awaiting-maintainer

    The ACCEPT above (15:35:23Z) ends with an explicit fence:

    ⛔ This card is not closed by this comment — the deliverable is a draft awaiting the
    maintainer's review and submission. It closes when that submission happens, or when the
    maintainer decides not to send it.

    Correct, and unchanged. But the card has been wearing pm:dispatched with an assignee ever since
    — telling the state machine a dev is in flight when the drafting work is finished and accepted.
    That is this seat's own half-state residue, and it is being cleared on the record rather than
    quietly.

    Why pm:awaiting-maintainer and not something else

    The state model's distinctions, applied one at a time:

    • ⛔ Not needs-user-decision — the decision is already made. Ruling A was recorded on
      2026-08-26 (「其他接受你的建议」): a seat drafts, the maintainer reviews and submits under their
      own name. Nothing is awaiting a judgement.
    • ⛔ Not pm:on-hold — that is "decided, and the answer is not now". The answer here is yes,
      send it
      .
    • ⛔ Not closed — the deliverable has not reached its destination.
    • ✅ pm:awaiting-maintainer — decision made; the only remaining action is one human action
      outside GitHub
      : posting the drafted issue to the upstream tsup repository under the
      maintainer's own name. That is precisely what this state is for.

    Per the state model, pm:awaiting-maintainer is mutually exclusive with the other pm:* states,
    so pm:dispatched comes off in the same write. The cross-repo ledger and the exit for this
    state belong to the 项目总监席 — this seat only enters the state.

    What the maintainer needs, in one place

    • The complete, copy-pasteable upstream issue text is in the comment at 15:31:09Z. It contains no
      ObjectStack issue numbers, links or jargon.
    • ⛔ Nothing has been posted to any repository outside this one.
    • Premise re-verified the day it was drafted, not recalled: this repo resolves tsup@8.5.1,
      which is also npm's dist-tags.latest, and upstream src/index.ts still registers only the
      message handler — no error, no exit.
    • The proposed two-line patch was applied and measured, not merely described: killed worker
      goes from exit 0 with a declaration-less dist/ to exit 1 with a real error, and the happy path
      still emits index.d.ts.

    One correction the draft already absorbs

    ⚠️ OOM is not the silent shape. An OOM'd worker emits ERR_WORKER_OUT_OF_MEMORY and exits
    loudly. This matters beyond the draft — it is why #13509 was filed: check-dts-emitted.mjs
    names OOM as the observed silent-build shape in both its header and its failure message, and leads
    its remediation with a --max-old-space-size retry. ⇒ a guard whose logic is correct, telling an
    operator to do something that cannot help, for a failure mode that is not the one occurring.
    ⛔ Ungraded, unassigned — triage's to route.


    Generated by Claude Code

  6. hotlong commented on Aug 31, 2026

    @hotlong
    Contributor

    ✅ SUBMITTED upstream — egoist/tsup#1410. Card closes.

    Executed by the director seat (session local_c32e5a25-0581-4665-952f-60c1be74b250) on the maintainer's in-session delegation, 2026-08-31, verbatim: 「帮我处理 os#12167」 — completing the 2026-08-26 ruling A (seat drafts, maintainer reviews and submits; the submission was performed via the maintainer-operated hotlong account, which the post carries).

    Pre-submission checks, run fresh:

    • Dedup re-run against egoist/tsup (tracker search, 12 nearest results reviewed): no existing report of the silent exit-0 shape — nearest neighbours remain the loud-path OOM reports chore(ci)(deps): bump dorny/paths-filter from 3 to 4 #920/[P3] mobile: responsive layout / mobile experience unverified #1325, referenced as related in the submitted text, exactly as the draft's own dedup found.
    • Tracker state: issues enabled, repo not archived.
    • Text: the 2026-08-30 corrected draft submitted verbatim (title + body between the BEGIN/END markers) — including the OOM-is-the-loud-path correction, the deterministic message-less-death repro, the two-line fix and its before/after verification matrix. No ObjectStack-internal references in the posted text.

    The in-repo guard (check-dts-emitted.mjs, #12076/#12078) stays in place as the compensating control until the upstream fix ships — as this card itself prescribed (complements, not alternatives).

    pm:awaiting-maintainer exit: the awaited maintainer action has happened; label off, closing completed.

  7. claude commented on Aug 31, 2026

    @claude
    Contributor

    Correction to this card's body, from a measurement taken today — posted by the domain:devx execution PM seat (#6023), session session_01Pk26oZ12t5N1hwGW1m1MgC.

    ⛔ This card's body is left as filed. It is the record of what was believed at the time, and rewriting it would falsify that record. This comment is the correction; the body stays.

    This card's body says, verbatim:

    OOM under memory pressure is the observed shape

    ⛔ That is not what happens. Measured on Node v22.22.2 / linux x64 against a harness carrying tsup's exact promise shape (a Worker with only message registered — no error, no exit):

    worker death mode event delivered process outcome
    process.exit(), CJS caller exit exit 0, zero output
    process.exit(), ESM caller (top-level await) exit exit 13 + "unsettled top-level await" warning
    heap OOM, per-worker resourceLimits error exit 1
    heap OOM, process-wide --max-old-space-size error exit 1

    ⇒ 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. An OOM'd DTS pass exits non-zero and prints a stack — it is the loud sibling, and turbo never caches it. Only the first row is silent, and the caller's module system is load-bearing: the same message-less death exits 13 with a warning under ESM and 0 in silence under CJS. tsup's CLI is CJS, which is why the observed shape was a silent one.

    ⭐ The practical consequence, and the reason this was worth correcting rather than shrugging at: the remedy that followed from the OOM reading — build with more headroom — can never converge, because the exit-0 shape is not a memory problem.

    ⚠️ What killed the worker in this card's original observation is NOT established by any of the above. All that is shown is that it ended without an error event (because that run exited 0), and therefore that memory pressure is not a demonstrated cause of this shape. ⛔ No cause is assigned here.

    The guard's header and its printed failure message — which cite this card — are corrected to match in #13898.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions