Skip to content

fix: preserve multiple Set-Cookie headers through the runtime - #16

Open
jonasnobile wants to merge 3 commits into
mainfrom
fix/preserve-multiple-set-cookie
Open

fix: preserve multiple Set-Cookie headers through the runtime#16
jonasnobile wants to merge 3 commits into
mainfrom
fix/preserve-multiple-set-cookie

Conversation

@jonasnobile

Copy link
Copy Markdown
Member

Problem

Headers.forEach folds same-name headers into a single comma-joined value. So when a worker response carries several Set-Cookie headers — the shape every cookie-based auth library produces (e.g. better-auth's session_token + cached session_data) — they collapsed into one corrupted cookie and the rest were silently dropped.

Effect: cookie-based auth is broken under pletivo dev / lopata (the docker dev path). The workerd-backed LOPATA=0 path was unaffected, which is why it went unnoticed.

Fix

Collect cookies via Headers.getSetCookie() and emit each one separately in the three places that serialize a Response:

  • vite-plugin writeResponse (Node res.writeHead — array value ⇒ one header per cookie)
  • worker-thread serializeResponse (entry.ts)
  • worker-thread serialize (rpc-shared.ts)

The deserialize side (makeResponse) already rebuilds via new Response(body, { headers }) from the [k,v][] array, which appends each pair — so multiple ['set-cookie', …] pairs round-trip correctly.

Test

Adds tests/set-cookie-e2e.test.ts + a set-cookie-worker fixture that returns two Set-Cookie headers and asserts both survive end-to-end through the running runtime.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XntvTQmXo7hrNv3UBqN3s3

jonasnobile and others added 2 commits July 1, 2026 10:10
Headers.forEach folds same-name headers into one comma-joined value, so a
worker response carrying several Set-Cookie cookies (e.g. an auth library's
session token + cached session) collapsed to a single corrupted cookie and the
others were dropped. This broke cookie-based auth under `pletivo dev`/lopata
(workerd-backed `LOPATA=0` was unaffected).

Collect cookies via Headers.getSetCookie() and emit each one separately in the
three places that serialize a Response:
- vite-plugin writeResponse (Node res.writeHead)
- worker-thread serializeResponse (entry.ts)
- worker-thread serialize (rpc-shared.ts)

Adds an e2e regression test (set-cookie-worker fixture) asserting both cookies
survive end-to-end.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XntvTQmXo7hrNv3UBqN3s3
Collapse the byte-identical Set-Cookie serialization blocks in
serializeResponse (entry.ts) and dispatchRpcFetch (rpc-shared.ts) into a
single serializeResponseHeaders() helper in serialize.ts, alongside the
existing request/response serialization primitives. Behavior unchanged;
future header/cookie fixes now live in one place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017pcBc1XDGY1f9ZoribTzLv
@jonasnobile
jonasnobile force-pushed the fix/preserve-multiple-set-cookie branch from ed25093 to 899b221 Compare July 1, 2026 08:11
…eHeaders

Review follow-up extending the Set-Cookie fix to the paths PR #16 missed:

- DO fetch responses (do-worker-entry) and worker-loader responses now use
  serializeResponseHeaders() instead of folding forEach/entries() loops
- vite-plugin writeResponse delegates to the helper and groups repeated keys
  generically, so the Set-Cookie invariant lives in one place
- tracing headersToRecord captures Set-Cookie as string[] instead of a
  folded value
- drop the dead getSetCookie?.() guard — Bun always implements it, and the
  fallback would have silently dropped cookies instead of failing loudly
- e2e test: two Set-Cookie headers on a DO response survive the bridge

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0143RmhobMFnePrzRm8RLqq6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant