fix(web): freeze response head before disposing an awaited renderToStream - #3292
Merged
Conversation
🦋 Changeset detectedLatest commit: edf891b The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Coverage Report for CI Build 34017406325Coverage remained the same at 71.814%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
ryansolid
added a commit
to solidjs/solid-vite-plugin
that referenced
this pull request
Sep 6, 2026
rc.7 (solidjs/solid#3292) freezes the response head when an awaited renderToStream completes, before the render is disposed, so the generated SSR entry no longer needs to commit the stub from onCompleteAll and authored entries need no hook. Peer floors move to ^2.0.0-rc.7; README caveat and the unreleased render-mode changeset drop the workaround wording. Against rc.6 the render-mode suite fails exactly the six async head assertions (status/header/Location, dev+prod); the rc.7 ride commit (catalog + lockfile + minimumReleaseAgeExclude) turns them green. Co-authored-by: Cursor <cursoragent@cursor.com>
ryansolid
force-pushed
the
fix-async-render-status
branch
from
September 6, 2026 06:43
75029ea to
701aac7
Compare
…ream `await renderToStream(...)` resolved AFTER disposing the render owner, while `event.response` was still uncommitted, so every scope-tied `httpStatus`/`httpHeader` cleanup retracted its declaration before the consumer ever saw the HTML: a page's `httpStatus(404)` came back 200 from `createSSRResponse(html, event)` and declared headers vanished. The pipe paths never hit this because `createSSRResponse` commits the stub on the shell's first write, before the final dispose. Treat completion as the awaited path's head-freeze point: the thenable commits the request's response stub (captured at render start — the thenable may be awaited outside the request scope it was started in) right before `dispose()`. `createSSRResponse` / `commitEventResponse` already pass an already-committed stub through untouched. Retraction semantics are unchanged — a scope disposed mid-render still retracts — and the failRender resolution path keeps the head open as before. Integrations no longer need the `onCompleteAll` commit workaround.
ryansolid
force-pushed
the
fix-async-render-status
branch
from
September 6, 2026 06:47
701aac7 to
edf891b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
await renderToStream(...)(the thenable that replacedrenderToStringAsync) resolved after disposing the render owner, whileevent.responsewas still uncommitted. Every scope-tiedhttpStatus/httpHeadercleanup therefore retracted its declaration before the consumer ever saw the HTML: a page callinghttpStatus(404, "Not Found")came back as a 200 fromcreateSSRResponse(html, event), and headers declared withhttpHeadervanished.The pipe /
pipeTo/readablepaths never hit this becausecreateSSRResponsecommits the stub on the shell's first write, i.e. before the finaldispose()runs (firstFlushed).Integrations (e.g.
@solidjs/vite-plugin's async render mode) currently work around it by committing the stub fromonCompleteAll, which happens to run just before the thenable'scomplete().Fix
Treat render completion as the awaited path's head-freeze point, mirroring what the shell flush does for the piped forms: the thenable commits the request's response stub via
commitResponseStubimmediately beforedispose(). The request event is captured atrenderToStreamentry (a silent lookup of the request scope's store,peekRequestEvent), not inthen(), because the thenable may legitimately be awaited outside the scope it was started in —await provideRequestEvent(event, () => renderToStream(...))is the storage module's own documented shape.createSSRResponse/commitEventResponsealready pass an already-committed stub through untouched, so consumers need no change.httpStatus/httpHeaderare untouched: a scope disposed mid-render (an errored, recovered boundary) still retracts, because the head is still open at that point.failRenderresolution path (a real error in a retry pass) deliberately keeps the head open as before: the render died, and the consumer keeps a writable head for whatever error response it builds around the partial HTML.next).Doc comments updated: the
then()contract (type + implementation),createSSRResponse(string results from an awaited render arrive committed),commitResponseStub, and thehttpStatus/httpHeaderjsdoc (server + client stubs) now state whencommittedflips on each path.Tests
packages/web/test/server/http-components.spec.tsx, new describe "awaited renderToStream: response head freezes at completion" (4 of 5 fail onnext— 200 instead of 404/410/500):Gates
pnpm --filter @solidjs/web test(client, server, hydrate configs): all greenpnpm --filter @solidjs/web test-types,build: greenscripts/size: identical numbers to the currentnexthead (theapp: render + one signal/app: CSRscenarios are already over cap onnextby 33 B / 30 B since feat(web): support responsive image preloads #3183 — unrelated to this PR)Changeset:
@solidjs/webpatch.