You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 1727827
Browse filesBrowse the repository at this point in the historyBrowse files
docs(cli): name the writer that actually made the first failing stderr write
`bin/run-dev.js` explained #14858's crash with "oclif's `displayWarnings()`
makes the first write". Re-traced with a `--import` observer that wraps
`process.stderr.write` and logs the call site of the first EPIPE-ing call:
node's OWN default `warning` handler (`internal/process/warning.js`:
`onWarning` -> `writeOut` -> `console.error`) makes write #1, and
`displayWarnings()` makes writes #2 and #3 of the same warning.
Every write on that path is a `console.error`, and the reason that is fatal
here while `bin/run.js` measured it harmless is not payload size. Console's
`ignoreErrors` keep-alive is installed by the write CALLBACK and only
`if (stream.listenerCount('error') === 0)`. `tsx` registers an off-thread
module-customization hook, so node pipes the hooks worker's stderr into
`process.stderr` and `Stream.prototype.pipe` prepends an `onerror` there; the
count is 1, the keep-alive never installs, `onerror` takes the first EPIPE and
re-emits it with nothing listening.
Controls, node 22.22.2, read end destroyed, one variable between the legs:
`console.error` alone 0/3, `module.register()` of a no-op hook plus the same
`console.error` 3/3, raw `process.stderr.write` 3/3. The shim as shipped is
0/3 (exit 2); with the #14858 listener ablated it is 3/3 (exit 1).
Comment text only. No behaviour changes, the listener stays exactly as it is,
and the three `displayWarnings()` sites that state listener TIMING rather than
authorship are untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8
`bin/run.js` no longer states Node's `console.error` protection as an unconditional fact — it is conditional, and the condition is a property of the process's listener census rather than of `console.error`.
6
+
7
+
The docblock over that file's `process.stderr``error` listener explained the last row of its measurement table with "`ignoreErrors` … parks a temporary `error` listener across the write — so oclif's warning blocks cannot crash this process at any size". Both halves needed correcting, and only the second one was visible:
8
+
9
+
- The listener parked *across* the write is not what saves anything. `kWriteToConsole`'s `finally` removes it before the completion arrives. What keeps the process alive is Console's write CALLBACK re-attaching a `noop`, and it does that only `if (stream.listenerCount('error') === 0)`.
10
+
- That count is 0 on this entry point and is not universal. `bin/run-dev.js` runs under `tsx`, which registers an off-thread module-customization hook; node pipes that worker's stderr into `process.stderr` and `Stream.prototype.pipe` prepends its own `onerror` there. With the count at 1 the keep-alive is never installed, `onerror` takes the first EPIPE and re-emits it with nothing listening, and **one short `console.error` crashes the process 3/3** — no payload size involved.
11
+
12
+
Measured on node 22.22.2 against a destroyed read end, one variable changed between the legs: `console.error` alone 0/3, `module.register()` of a no-op hook plus the same `console.error` 3/3, a raw `process.stderr.write` 3/3.
13
+
14
+
Comment text only. No behaviour, no accepted arguments, no exported member and no runtime contract changes; the listener itself, its name and the pin that waits for it are untouched. It publishes because npm packs a `bin` target regardless of `files[]` (#14874) — measured: the tarball ships `bin/run.js` verbatim and the corrected prose is in it — which is why this is a `patch` rather than a `skip-changeset`.
0 commit comments