Skip to content

ctx.setCookie() from a handler emits nothing: cookiesToSet is drained before the handler runs #1944

Description

@glennmichael123

createContext in ssr.js gives handlers a setCookie(name, value, options). It appends to a local cookiesToSet array. That array is copied into responseHeaders as the last statement of createContext, before any handler has run:

setCookie(name, value, options = {}) {
  cookiesToSet.push(serializeCookie(name, value, options))
},
...
};                                   // <- ctx object literal ends here

if (cookiesToSet.length > 0)
  cookiesToSet.forEach((cookie) => {
    responseHeaders.append('Set-Cookie', cookie)
  })

return ctx                           // <- handlers run after this

Anything a handler pushes lands in an array that is never read again, so ctx.setCookie(...) from a route handler emits no Set-Cookie.

Why it looks like it works

The session cookie is pushed inside createContext itself, before the drain:

let sessionId = cookies[sessionCookieName]
if (!sessionId) {
  sessionId = generateSessionId()
  cookiesToSet.push(serializeCookie(sessionCookieName, sessionId, { httpOnly: true, ... }))
}

So sessions work, and cookies appear on responses, and the one call that is actually broken is the one an application makes. A smoke test that checks "does the server set cookies" passes.

Suggested fix

Drain at response-build time rather than at context-build time. Either append directly in setCookie:

setCookie(name, value, options = {}) {
  responseHeaders.append('Set-Cookie', serializeCookie(name, value, options))
},

which makes the array unnecessary, or keep the array and flush it after the handler chain resolves, where the Response is assembled.

The direct-append version looks strictly better here: responseHeaders is already a live Headers on the context, and Headers.append is what the existing drain calls anyway, so there is no ordering left to get wrong.

Related

stx#1927 fixed the same class of problem on the page-action path (an action's cookies had no way out) and shipped in 0.2.177. This is the sibling case on the SSR handler path.

Environment

@stacksjs/stx 0.2.198, bun 1.3.14, macOS 15.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions