Summary
When an endpoint sets multiple cookies (e.g. via ctx.setCookie()), the Response built by toResponse() ends up with only one Set-Cookie header. The rest are dropped, so the browser never receives them (e.g. session cookie is lost and the user does not appear logged in).
Cause
In to-response (and the built dist/to-response.mjs), headers are merged by iterating and calling headers.set(key, value). For repeated header names, Headers#set() replaces the previous value, so only the last Set-Cookie is kept.
Places affected:
- Response branch (when
data instanceof Response): init.headers.forEach((value, key) => { data.headers.set(key, value); }) overwrites previous set-cookie values.
- isJSONResponse branch: the three loops that copy
routerResponse.headers, data.headers, and init.headers all use headers.set(key, value) in the same way.
HTTP allows multiple Set-Cookie headers (one per cookie). The Fetch Headers API allows multiple values for the same name only when using append(), not set().
Impact
- Auth/session flows that set several cookies (e.g. session token + session_data + dont_remember) only send one cookie to the client.
- Typically the session token is set first and a cache cookie last, so the last one survives and the session cookie is lost → user is not logged in after sign-in.
Suggested fix
When copying/merging headers, use append(key, value) for the set-cookie header name and set(key, value) for others (e.g. a small copyHeaders(target, source) helper used in all merge sites in to-response). That preserves every Set-Cookie line in the final Response.
Summary
When an endpoint sets multiple cookies (e.g. via
ctx.setCookie()), theResponsebuilt bytoResponse()ends up with only oneSet-Cookieheader. The rest are dropped, so the browser never receives them (e.g. session cookie is lost and the user does not appear logged in).Cause
In
to-response(and the builtdist/to-response.mjs), headers are merged by iterating and callingheaders.set(key, value). For repeated header names,Headers#set()replaces the previous value, so only the lastSet-Cookieis kept.Places affected:
data instanceof Response):init.headers.forEach((value, key) => { data.headers.set(key, value); })overwrites previousset-cookievalues.routerResponse.headers,data.headers, andinit.headersall useheaders.set(key, value)in the same way.HTTP allows multiple
Set-Cookieheaders (one per cookie). The FetchHeadersAPI allows multiple values for the same name only when usingappend(), notset().Impact
Suggested fix
When copying/merging headers, use
append(key, value)for theset-cookieheader name andset(key, value)for others (e.g. a smallcopyHeaders(target, source)helper used in all merge sites into-response). That preserves everySet-Cookieline in the finalResponse.