Repository navigation
fix(cloudflare): close request scopes for bodyless responses - #1649
Conversation
…esponse on GET A Worker handler that answers with `HttpServerResponse.raw(new Response(...))` saw a status or headers set later through the Effect response APIs, for example by a pre-response handler, reach HEAD answers but not GET answers. `HttpServerResponse.toWeb` hands a Raw web `Response` to the client as the object it is, so the Effect-level status is dropped on GET, while the HEAD path (`withoutBody`) builds a fresh response from the Effect-level fields. The adapter now adopts the native object's status, status text and headers into the `HttpServerResponse` before the pre-response handlers run, so those see the real status and whatever they set is a difference against the native object. On output, a Raw web `Response` nothing changed is returned untouched, a 101 always is, and otherwise a new `Response` is built over the same body stream with the Effect-level status and headers, keeping the native `Set-Cookie` multiplicity and appending Effect-level cookies. GET and HEAD answer from the same rule. Closes alchemy-run#1648 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks for reporting and reproducing this. We revised the approach after comparing it with Effect's existing response APIs. For ordinary native HTTP responses that need to participate in Effect middleware, - HttpServerResponse.raw(nativeResponse)
+ HttpServerResponse.fromWeb(nativeResponse)That lets Effect handle conversion in both directions without Alchemy maintaining a second implementation. We removed the custom adoption/merge/conversion helpers. During review, we also found that the adoption step overwrote an explicit status set before the handler returned: a requested The remaining production change matches Effect's web handler: transfer request-scope ownership to a stream only when the response will actually send a body. Otherwise HEAD and 204/205/304 responses can leave request finalizers unexecuted. The fixture now uses This is a narrower change than the original proposal: it addresses the middleware use case through |
|
Install the packages built from this commit: alchemypnpm install https://pkg.alchemy.run/alchemy/pr:1649:541f647@alchemy.run (6)pnpm install https://pkg.alchemy.run/@alchemy.run/better-auth/pr:1649:541f647pnpm install https://pkg.alchemy.run/@alchemy.run/cloudflare-runtime/pr:1649:541f647pnpm install https://pkg.alchemy.run/@alchemy.run/frontend-frameworks/pr:1649:541f647pnpm install https://pkg.alchemy.run/@alchemy.run/node-utils/pr:1649:541f647pnpm install https://pkg.alchemy.run/@alchemy.run/floci/pr:1649:541f647pnpm install https://pkg.alchemy.run/@alchemy.run/pkg/pr:1649:541f647Transitive Dependencies (18)pnpm install https://pkg.alchemy.run/@distilled.cloud/acme/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/aws/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/axiom/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/cloudflare/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/core/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/doppler/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/fly-io/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/gcp/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/hetzner/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/iceberg/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/infisical/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/neon/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/planetscale/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/prisma/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/railway/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/stripe/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/zerossl/pr:1649:541f647pnpm install https://pkg.alchemy.run/@distilled.cloud/github/pr:1649:541f647Published |
A Worker's request finalizers never ran for HEAD, 204, 205 and 304 responses. The request scope was always handed to the response stream, but those responses send no body, so nothing closed it:
The scope now goes to the stream only when a body will be sent, matching Effect's own web handler:
For a native
Responsethat should go through Effect middleware (status, headers, cookies), useHttpServerResponse.fromWeb(nativeResponse)overraw(nativeResponse).rawstays a passthrough, which keeps WebSocket upgrades working. This PR does not changeraw's GET/HEAD inconsistency reported in #1648.