Summary
Refusal paths that run after createEvent return their response directly instead of through commitEventResponse, so every Set-Cookie the integration wrote onto the event's response stub is silently dropped.
Tested against next @ fa137614, built from source, Node 24.19.
Reproduction
A createEvent that writes a cookie — the shape a session-rotating or CSRF-token middleware has — plus a request that is refused after the event exists:
const createEvent = request => {
const event = createRequestEvent(request);
event.response.headers.append("Set-Cookie", "sid=abc; Path=/");
return event;
};
// over the maxArguments cap
handleServerFunctionRequest(
new Request(url, { method: "POST", headers: H, body: JSON.stringify(Array.from({length: 2000}, (_, i) => i)) }),
{ createEvent }
);
maxArguments 400 -> status=400 set-cookie=[]
control 200 -> status=200 set-cookie=["sid=abc; Path=/"]
The same holds for the other post-createEvent exits — the no-JS 400, the malformed-body 400, and the 413.
Why it matters
The write is not merely lost, it is lost silently: no warning, no throw, and the stub is never committed, so the post-commit instrumentation that normally reports a dropped header write never fires either.
The realistic consequence is a rotated session or a freshly minted CSRF token that the browser never receives, on exactly the requests where something already went wrong. The next request then carries stale credentials, and the failure looks like it belongs to that request rather than the refused one.
Reachability: ordinary application code — it needs only an integration that writes a cookie in createEvent, which is the documented place to do it. Impact is modest but the silence is the problem.
Options
- Route every post-
createEvent exit through commitEventResponse. Mechanical, and it makes the rule "once an event exists, its response head is folded exactly once" true without exception. This is what the success path already does.
- Fold at a single exit point — have the handler's outermost frame commit whatever response the inner logic produced, so no individual refusal has to remember. Fewer call sites to keep in step; a larger refactor of the return paths.
- Warn in dev when an uncommitted stub carrying cookies is discarded. Turns a silent drop into a loud one without changing behaviour. Worth having regardless of 1 or 2, since it catches future exits that forget.
- Document that refusals do not carry integration cookies. I'd argue against it alone: the invariant it asks authors to remember is invisible at the point where the cookie is written.
(1) plus (3) looks right — the fix is small and the warning stops the class from recurring.
Happy to send a PR. The regression test shape I'd use is the reproduction above across all four refusal exits, asserting the cookie survives, with the 200 path as the control.
Summary
Refusal paths that run after
createEventreturn their response directly instead of throughcommitEventResponse, so everySet-Cookiethe integration wrote onto the event's response stub is silently dropped.Tested against
next@fa137614, built from source, Node 24.19.Reproduction
A
createEventthat writes a cookie — the shape a session-rotating or CSRF-token middleware has — plus a request that is refused after the event exists:The same holds for the other post-
createEventexits — the no-JS 400, the malformed-body 400, and the 413.Why it matters
The write is not merely lost, it is lost silently: no warning, no throw, and the stub is never committed, so the post-commit instrumentation that normally reports a dropped header write never fires either.
The realistic consequence is a rotated session or a freshly minted CSRF token that the browser never receives, on exactly the requests where something already went wrong. The next request then carries stale credentials, and the failure looks like it belongs to that request rather than the refused one.
Reachability: ordinary application code — it needs only an integration that writes a cookie in
createEvent, which is the documented place to do it. Impact is modest but the silence is the problem.Options
createEventexit throughcommitEventResponse. Mechanical, and it makes the rule "once an event exists, its response head is folded exactly once" true without exception. This is what the success path already does.(1) plus (3) looks right — the fix is small and the warning stops the class from recurring.
Happy to send a PR. The regression test shape I'd use is the reproduction above across all four refusal exits, asserting the cookie survives, with the 200 path as the control.