Minimal reproduction: when a "use server" RPC fails below the handler
(the response is a 500 without an X-Error header), the client-side promise
resolves instead of rejecting, so callers mistake total server failure for
success.
pnpm install
pnpm devOpen http://localhost:3000 and click "Call server function". The page shows:
BUG: the promise RESOLVED although POST /_server failed with a 500. Result: undefined
while the terminal logs the import-time crash and POST /_server returns 500.
fetchServerFunction in @solidjs/start/dist/fns/client.js only
throws when the response carries an X-Error header, which is set when the
handler itself throws. It never checks response.ok, so any failure that
happens before the handler runs resolves the promise: with the parsed error
body when its content type matches one of the parse branches (nitro's
application/json error payload in production), or with undefined when
none matches (this repro's dev-server HTML error page, hence
Result: undefined in the UI):
if (response.headers.has("X-Error")) {
throw result;
}
return result; // <- a 500 without X-Error falls through to hereAny 500 produced below the server function handler:
- A server function module that crashes at import time on the deployment
runtime. This is how we hit it in production: importing the
pinologger into a server function module deployed on Cloudflare Pages. pino initializes its writersonic-boomat import time, which callsnode:utilinherits(SonicBoom, EventEmitter). Under workerd'snodejs_compatthe bundledEventEmitterinterop had noprototype, so the whole server function chunk threw aTypeErrorbefore any handler ran. The pages kept rendering (they were prerendered at build time on Node, where pino loads fine), onlyPOST /_serverdied. Users submitted a contact form, saw the success state, and every message was silently lost. - A reverse proxy, CDN, or WAF answering with its own 5xx error page.
- A crashed or out-of-memory worker.
src/broken.ts simulates the first case with a module that throws at import
time on the server only.
The promise should reject, like a handler-thrown error does, and like server action/RPC clients in Next.js, TanStack Start, Remix, and SvelteKit behave on transport-level failures.
- solidjs/solid-start#2261 (fix for v2, including regression tests)
- solidjs/solid-start#2159 (the same fix, merged onto
the
1.xbranch) - solidjs/solid-start#1434 (feature request for a
Response.okcheck) - solidjs/solid-start#1898 (transport-level error body breaking the stream reader, where the fix only covers malformed seroval streams, not this case)
This repro runs @solidjs/start 2.0.0-rc.5 on plain Vite and Nitro v3. The
same X-Error-only logic was present in 1.x, where it was fixed by #2159, but
that fix was never ported to the rewritten v2 client, so the bug is back.