Repository navigation
Description
Describe the bug
If you pass signal to renderToStream and abort it while the stream is still pending (client closed the tab), the tree gets disposed inside the abort listener. If any onCleanup throws at that point, the error doesn't go to onError. It goes out of the EventTarget dispatch, and Node and Bun treat it as an uncaught exception, so the whole server process exits.
Wrapping controller.abort() in try/catch doesn't help, because an error thrown by an abort listener is reported, not rethrown to whoever called abort().
How I ran into it: TanStack Query's QueryClientProvider reads props.client in its cleanup. In my app that's a getter that looks up the current request with getRequestEvent(). The abort listener runs from the socket close, outside the request's async context, so the getter throws and the server dies on the first client that disconnects mid-stream. I fixed my side by aborting inside provideRequestEvent, but any other cleanup that throws will still take the process down.
Your Example Website or App
Single file repro below, no app needed.
Steps to Reproduce the Bug or Issue
- Save this as
repro.mjsnext tosolid-jsand@solidjs/web2.0.0-rc.14:
import { createComponent, Loading, createMemo, onCleanup } from "solid-js";
import { renderToStream } from "@solidjs/web";
const controller = new AbortController();
const never = new Promise(() => {});
const stream = renderToStream(
() => {
onCleanup(() => {
throw new Error("cleanup threw");
});
return createComponent(Loading, {
fallback: "waiting",
get children() {
const value = createMemo(async () => never);
return [() => value()];
},
});
},
{ signal: controller.signal, onError: (error) => console.log("onError saw", error.message) },
);
const { readable, writable } = new TransformStream();
stream.pipeTo(writable);
await readable.getReader().read();
controller.abort();- Run
NODE_ENV=production node repro.mjs - The process exits with an uncaught
Error: cleanup threw.onErroris never called.
Same result on Bun 1.4.3.
Expected behavior
abandon() should catch errors thrown while disposing the root and report them through onError (or reportServerError), the same way it already handles serializer.close() a few lines below. A client disconnecting shouldn't be able to kill the server through cleanup code.
Platform
- OS: Linux (Arch)
- Runtime: Node 24.21.0, Bun 1.4.3
solid-js2.0.0-rc.14,@solidjs/web2.0.0-rc.14 (server production build)
Additional context
The listener is onAbort around line 2153 of @solidjs/web/dist/server.js in rc.14. Since abandon() is only reachable through module private symbols, the signal is the only way to stop a render from outside, so callers can't work around this.
Activity
Merge pull request #4034 from solidjs/claude/project-thread-zclbh4
ryansolid commentedon Oct 13, 2026
Generated by Claude Code
fix(web): contain completion cleanup and drain failed pipeTo consumers
fix(web): route renderToStream serializer and cleanup errors to the a…
fix(web): contain a cleanup that throws during stream teardown (solid…