Which project does this relate to?
Start
Describe the bug
Headers set through the Server Function response context are lost when the final serialized Server Function Response has a non-2xx status.
This is status-dependent rather than specific to whether the Server Function returns a value or throws:
- For 2xx serialized responses, h3 merges the response-context headers during final response processing.
- For non-2xx serialized responses, h3 skips that merge because
Response.ok is false.
- TanStack Start currently preserves
Set-Cookie separately for non-2xx responses, but other response-context headers are lost.
Minimal example:
import { createServerFn } from "@tanstack/react-start";
import {
getResponseHeaders,
setResponseStatus,
} from "@tanstack/react-start/server";
export const failingFn = createServerFn({ method: "GET" }).handler(
async () => {
getResponseHeaders().set("x-custom-header", "true");
setResponseStatus(401);
throw new Error("Expected error");
},
);
After calling failingFn from the client, the resulting /_serverFn/... response has status 401 and the serialization headers, but does not contain x-custom-header: true.
An ordinary serialized return value is affected as well if its response status is set to a non-2xx value.
Complete minimal reproducer
To run it locally:
git clone https://github.com/6mn12j/tanstack-router-7914-repro.git
cd tanstack-router-7914-repro
npm ci
npm run dev
Steps to Reproduce the Bug
- Open the Railway reproduction, or clone and run the repository locally.
- Click Run reproduction once.
- Inspect both the rendered evidence and the resulting
/_serverFn/... request in the browser Network panel.
- Observe that the response status is
401 and x-tss-serialized is true, while x-custom-header is missing.
- The same status-dependent loss applies to non-streaming, streaming/framed, and serialized error responses when the final HTTP status is non-2xx.
Expected behavior
Headers previously added to the Server Function response context should be preserved in the final serialized response for non-2xx statuses as well as 2xx statuses.
This matters beyond the application-specific reproduction. Headers such as WWW-Authenticate, Retry-After, CORS headers, rate-limit metadata, request IDs, and trace metadata are often required specifically on non-2xx responses.
The final merge should have one consistent conflict policy:
- Framework-owned serialization headers such as
Content-Type and x-tss-serialized should take precedence.
- Headers invalidated by replacing or streaming the response body, such as a stale
Content-Length, should be excluded or recalculated.
- Multiple
Set-Cookie values should be appended exactly once.
Screenshots or Videos
No response
Platform
- Router / Start Version: @tanstack/react-start 1.167.50
- Resolved @tanstack/start-server-core: 1.167.22
- OS: macOS
- Browser: Chrome
- Bundler: Vite
Additional context
In our application, a backend error logger sets x-logged before rethrowing a 401 ApiError. An outer framework logger checks this marker to prevent duplicate logging.
Because the header is lost from the non-2xx response, both logging layers emit an entry for the same request. In Datadog, the paired events appear approximately 7–20 ms apart:
- Backend 401 ApiError
- TanStack Start Framework-level error
The full response path appears to be:
server-functions-handler.ts creates serialized JSON and framed Response objects with status and framework serialization headers.
request-response.ts handles final response attachment. For non-2xx responses, it currently carries over Set-Cookie only.
h3 response finalization merges prepared response headers only when the returned Response is ok; it returns non-OK responses without that merge.
Could response-context headers be merged once at the final response boundary for both 2xx and non-2xx responses, with framework-owned and body-dependent headers handled according to the rules above?
A response-finalization fix may avoid duplicate merging of 2xx headers or Set-Cookie values that could occur if each serialization branch copied the response-context headers independently.
Useful regression coverage would be a status matrix such as 200 and 401 across:
- Non-streaming serialized values
- Streaming/framed values
- Serialized errors
- Raw
Response returns
Related issues that appear adjacent but concern request-header propagation or cookie handling:
Which project does this relate to?
Start
Describe the bug
Headers set through the Server Function response context are lost when the final serialized Server Function
Responsehas a non-2xx status.This is status-dependent rather than specific to whether the Server Function returns a value or throws:
Response.okisfalse.Set-Cookieseparately for non-2xx responses, but other response-context headers are lost.Minimal example:
After calling
failingFnfrom the client, the resulting/_serverFn/...response has status401and the serialization headers, but does not containx-custom-header: true.An ordinary serialized return value is affected as well if its response status is set to a non-2xx value.
Complete minimal reproducer
To run it locally:
git clone https://github.com/6mn12j/tanstack-router-7914-repro.git cd tanstack-router-7914-repro npm ci npm run devSteps to Reproduce the Bug
/_serverFn/...request in the browser Network panel.401andx-tss-serializedistrue, whilex-custom-headeris missing.Expected behavior
Headers previously added to the Server Function response context should be preserved in the final serialized response for non-2xx statuses as well as 2xx statuses.
This matters beyond the application-specific reproduction. Headers such as
WWW-Authenticate,Retry-After, CORS headers, rate-limit metadata, request IDs, and trace metadata are often required specifically on non-2xx responses.The final merge should have one consistent conflict policy:
Content-Typeandx-tss-serializedshould take precedence.Content-Length, should be excluded or recalculated.Set-Cookievalues should be appended exactly once.Screenshots or Videos
No response
Platform
Additional context
In our application, a backend error logger sets
x-loggedbefore rethrowing a 401ApiError. An outer framework logger checks this marker to prevent duplicate logging.Because the header is lost from the non-2xx response, both logging layers emit an entry for the same request. In Datadog, the paired events appear approximately 7–20 ms apart:
The full response path appears to be:
server-functions-handler.tscreates serialized JSON and framedResponseobjects with status and framework serialization headers.request-response.tshandles final response attachment. For non-2xx responses, it currently carries overSet-Cookieonly.h3response finalization merges prepared response headers only when the returnedResponseisok; it returns non-OK responses without that merge.Could response-context headers be merged once at the final response boundary for both 2xx and non-2xx responses, with framework-owned and body-dependent headers handled according to the rules above?
A response-finalization fix may avoid duplicate merging of 2xx headers or
Set-Cookievalues that could occur if each serialization branch copied the response-context headers independently.Useful regression coverage would be a status matrix such as
200and401across:ResponsereturnsRelated issues that appear adjacent but concern request-header propagation or cookie handling: