[Start] Preserve response-context headers in non-2xx serialized Server Function responses
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 15.1k
- Forks
- 1.9k
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 143
Description
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.okisfalse. - TanStack Start currently preserves
Set-Cookieseparately 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
- Live reproduction: https://tanstack-router-7914-repro-production.up.railway.app/
- Source code and local setup: https://github.com/6mn12j/tanstack-router-7914-repro
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
401andx-tss-serializedistrue, whilex-custom-headeris 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-Typeandx-tss-serializedshould take precedence. - Headers invalidated by replacing or streaming the response body, such as a stale
Content-Length, should be excluded or recalculated. - Multiple
Set-Cookievalues 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.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-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
Responsereturns
Related issues that appear adjacent but concern request-header propagation or cookie handling:
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with packages/start-server-core/src/request-response.ts and trace how server-functions-handler.ts creates serialized and framed responses. Compare final response handling for 2xx and non-2xx statuses, then inspect the existing response tests if available. Done means response-context headers are preserved across the listed status and response forms without duplicate cookies or stale body-dependent headers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100