Skip to content

response reconciliation#7588

Open
schiller-manuel wants to merge 1 commit into
mainfrom
response-reconciliation
Open

response reconciliation#7588
schiller-manuel wants to merge 1 commit into
mainfrom
response-reconciliation

Conversation

@schiller-manuel

@schiller-manuel schiller-manuel commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

fixes #5107
fixes #5464
fixes #5407

Summary by CodeRabbit

  • New Features

    • Improved response reconciliation across server routes, SSR, and server functions (cookies, headers, status).
    • Enhanced session handling and cookie helpers for robust persistence and recovery.
    • Added default error/catch UI and route utilities for E2E apps.
  • Bug Fixes

    • Safer server-entry error recovery and protected header handling to avoid overwrites.
    • Fixed middleware/response ordering and multi-cookie preservation across redirects/replacements.
  • Documentation

    • Updated server entry guide and added reconciliation docs.
  • Tests

    • New comprehensive E2E and unit tests covering reconciliation and session behavior.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 83e61506-682d-4aeb-b86c-aef7852a71fe

📥 Commits

Reviewing files that changed from the base of the PR and between fe11405 and 97e14ca.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (86)
  • .changeset/start-response-state.md
  • docs/start/framework/react/guide/server-entry-point.md
  • e2e/react-start/response-reconciliation/.gitignore
  • e2e/react-start/response-reconciliation/.prettierignore
  • e2e/react-start/response-reconciliation/package.json
  • e2e/react-start/response-reconciliation/playwright.config.ts
  • e2e/react-start/response-reconciliation/rsbuild.config.ts
  • e2e/react-start/response-reconciliation/server.js
  • e2e/react-start/response-reconciliation/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/response-reconciliation/src/components/NotFound.tsx
  • e2e/react-start/response-reconciliation/src/routeTree.gen.ts
  • e2e/react-start/response-reconciliation/src/router.tsx
  • e2e/react-start/response-reconciliation/src/routes/__root.tsx
  • e2e/react-start/response-reconciliation/src/routes/api/-response.ts
  • e2e/react-start/response-reconciliation/src/routes/api/base.ts
  • e2e/react-start/response-reconciliation/src/routes/api/bulk-headers.ts
  • e2e/react-start/response-reconciliation/src/routes/api/clear-returned-headers.ts
  • e2e/react-start/response-reconciliation/src/routes/api/direct-mutation-visible.ts
  • e2e/react-start/response-reconciliation/src/routes/api/explicit-set-cookie-header.ts
  • e2e/react-start/response-reconciliation/src/routes/api/get-response-header-helper.ts
  • e2e/react-start/response-reconciliation/src/routes/api/get-response-headers-helper.ts
  • e2e/react-start/response-reconciliation/src/routes/api/multiple-cookies.ts
  • e2e/react-start/response-reconciliation/src/routes/api/null-body-status.ts
  • e2e/react-start/response-reconciliation/src/routes/api/readonly-after-next.ts
  • e2e/react-start/response-reconciliation/src/routes/api/redirect-with-cookies.ts
  • e2e/react-start/response-reconciliation/src/routes/api/remove-returned-header.ts
  • e2e/react-start/response-reconciliation/src/routes/api/replace-after-direct-mutation.ts
  • e2e/react-start/response-reconciliation/src/routes/api/replace-explicit-set-cookie.ts
  • e2e/react-start/response-reconciliation/src/routes/api/route-after-next.ts
  • e2e/react-start/response-reconciliation/src/routes/api/route-before-next.ts
  • e2e/react-start/response-reconciliation/src/routes/api/same-boundary-conflict.ts
  • e2e/react-start/response-reconciliation/src/routes/api/throw-after-status.ts
  • e2e/react-start/response-reconciliation/src/routes/api/two-returned-responses.ts
  • e2e/react-start/response-reconciliation/src/routes/index.tsx
  • e2e/react-start/response-reconciliation/src/routes/server-functions.tsx
  • e2e/react-start/response-reconciliation/src/routes/ssr.tsx
  • e2e/react-start/response-reconciliation/src/server-entry.ts
  • e2e/react-start/response-reconciliation/src/start.ts
  • e2e/react-start/response-reconciliation/src/styles/app.css
  • e2e/react-start/response-reconciliation/tests/response-reconciliation.spec.ts
  • e2e/react-start/response-reconciliation/tsconfig.json
  • e2e/react-start/response-reconciliation/vite.config.ts
  • e2e/react-start/session-handling/.gitignore
  • e2e/react-start/session-handling/.prettierignore
  • e2e/react-start/session-handling/package.json
  • e2e/react-start/session-handling/playwright.config.ts
  • e2e/react-start/session-handling/rsbuild.config.ts
  • e2e/react-start/session-handling/server.js
  • e2e/react-start/session-handling/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/session-handling/src/components/NotFound.tsx
  • e2e/react-start/session-handling/src/routeTree.gen.ts
  • e2e/react-start/session-handling/src/router.tsx
  • e2e/react-start/session-handling/src/routes/__root.tsx
  • e2e/react-start/session-handling/src/routes/api/session-clear.ts
  • e2e/react-start/session-handling/src/routes/api/session-cookie-disabled.ts
  • e2e/react-start/session-handling/src/routes/api/session-header.ts
  • e2e/react-start/session-handling/src/routes/api/session-helper-cookie.ts
  • e2e/react-start/session-handling/src/routes/api/session-middleware.ts
  • e2e/react-start/session-handling/src/routes/api/session-named.ts
  • e2e/react-start/session-handling/src/routes/api/session-seal.ts
  • e2e/react-start/session-handling/src/routes/api/session.ts
  • e2e/react-start/session-handling/src/routes/index.tsx
  • e2e/react-start/session-handling/src/routes/server-functions.tsx
  • e2e/react-start/session-handling/src/routes/ssr.tsx
  • e2e/react-start/session-handling/src/session.ts
  • e2e/react-start/session-handling/src/start.ts
  • e2e/react-start/session-handling/src/styles/app.css
  • e2e/react-start/session-handling/tests/session-handling.spec.ts
  • e2e/react-start/session-handling/tsconfig.json
  • e2e/react-start/session-handling/vite.config.ts
  • packages/react-start/src/default-entry/server.ts
  • packages/solid-start/src/default-entry/server.ts
  • packages/start-plugin-core/src/vite/plugin.ts
  • packages/start-plugin-core/tests/entry-planning.test.ts
  • packages/start-server-core/docs/RECONCILIATION.md
  • packages/start-server-core/package.json
  • packages/start-server-core/skills/start-server-core/SKILL.md
  • packages/start-server-core/src/createStartHandler.ts
  • packages/start-server-core/src/headers.ts
  • packages/start-server-core/src/internal-request-response.ts
  • packages/start-server-core/src/request-response.ts
  • packages/start-server-core/src/server-functions-handler.ts
  • packages/start-server-core/tests/createStartHandler.test.ts
  • packages/start-server-core/tests/server-functions-handler.test.ts
  • packages/start-server-core/vite.config.ts
  • packages/vue-start/src/default-entry/server.ts
✅ Files skipped from review due to trivial changes (15)
  • e2e/react-start/session-handling/src/styles/app.css
  • e2e/react-start/response-reconciliation/src/routes/api/two-returned-responses.ts
  • e2e/react-start/response-reconciliation/src/routes/api/route-after-next.ts
  • e2e/react-start/response-reconciliation/tsconfig.json
  • e2e/react-start/session-handling/.prettierignore
  • packages/start-plugin-core/src/vite/plugin.ts
  • e2e/react-start/session-handling/package.json
  • e2e/react-start/response-reconciliation/src/styles/app.css
  • e2e/react-start/response-reconciliation/.prettierignore
  • .changeset/start-response-state.md
  • e2e/react-start/response-reconciliation/.gitignore
  • e2e/react-start/session-handling/.gitignore
  • packages/start-server-core/skills/start-server-core/SKILL.md
  • e2e/react-start/response-reconciliation/package.json
  • e2e/react-start/response-reconciliation/src/routeTree.gen.ts
🚧 Files skipped from review as they are similar to previous changes (63)
  • e2e/react-start/session-handling/src/routes/api/session.ts
  • e2e/react-start/session-handling/src/routes/index.tsx
  • e2e/react-start/response-reconciliation/src/routes/api/direct-mutation-visible.ts
  • packages/vue-start/src/default-entry/server.ts
  • e2e/react-start/session-handling/src/routes/api/session-cookie-disabled.ts
  • e2e/react-start/session-handling/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/session-handling/vite.config.ts
  • e2e/react-start/response-reconciliation/src/routes/api/readonly-after-next.ts
  • e2e/react-start/response-reconciliation/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/response-reconciliation/src/routes/api/null-body-status.ts
  • packages/start-server-core/vite.config.ts
  • e2e/react-start/response-reconciliation/src/components/NotFound.tsx
  • e2e/react-start/response-reconciliation/vite.config.ts
  • e2e/react-start/response-reconciliation/src/router.tsx
  • e2e/react-start/response-reconciliation/src/routes/api/clear-returned-headers.ts
  • packages/react-start/src/default-entry/server.ts
  • e2e/react-start/response-reconciliation/src/routes/api/multiple-cookies.ts
  • e2e/react-start/response-reconciliation/src/routes/api/route-before-next.ts
  • e2e/react-start/response-reconciliation/src/routes/api/explicit-set-cookie-header.ts
  • e2e/react-start/session-handling/src/routes/api/session-seal.ts
  • e2e/react-start/response-reconciliation/src/routes/api/replace-after-direct-mutation.ts
  • e2e/react-start/session-handling/src/routes/api/session-header.ts
  • e2e/react-start/session-handling/src/router.tsx
  • e2e/react-start/response-reconciliation/src/routes/index.tsx
  • packages/solid-start/src/default-entry/server.ts
  • e2e/react-start/session-handling/src/routes/api/session-helper-cookie.ts
  • e2e/react-start/session-handling/src/routes/api/session-clear.ts
  • e2e/react-start/session-handling/src/routes/__root.tsx
  • e2e/react-start/session-handling/server.js
  • e2e/react-start/response-reconciliation/src/routes/api/get-response-header-helper.ts
  • packages/start-plugin-core/tests/entry-planning.test.ts
  • e2e/react-start/response-reconciliation/src/start.ts
  • e2e/react-start/session-handling/src/components/NotFound.tsx
  • e2e/react-start/response-reconciliation/rsbuild.config.ts
  • e2e/react-start/response-reconciliation/src/routes/api/remove-returned-header.ts
  • e2e/react-start/response-reconciliation/src/routes/__root.tsx
  • e2e/react-start/response-reconciliation/src/routes/api/base.ts
  • e2e/react-start/response-reconciliation/src/routes/api/same-boundary-conflict.ts
  • e2e/react-start/session-handling/src/routeTree.gen.ts
  • e2e/react-start/session-handling/tsconfig.json
  • e2e/react-start/session-handling/playwright.config.ts
  • e2e/react-start/response-reconciliation/src/server-entry.ts
  • packages/start-server-core/src/request-response.ts
  • e2e/react-start/response-reconciliation/src/routes/api/-response.ts
  • e2e/react-start/response-reconciliation/src/routes/server-functions.tsx
  • e2e/react-start/response-reconciliation/server.js
  • e2e/react-start/session-handling/rsbuild.config.ts
  • e2e/react-start/response-reconciliation/src/routes/api/get-response-headers-helper.ts
  • packages/start-server-core/src/headers.ts
  • docs/start/framework/react/guide/server-entry-point.md
  • e2e/react-start/session-handling/src/session.ts
  • e2e/react-start/session-handling/src/routes/ssr.tsx
  • packages/start-server-core/tests/createStartHandler.test.ts
  • e2e/react-start/session-handling/tests/session-handling.spec.ts
  • e2e/react-start/session-handling/src/start.ts
  • e2e/react-start/response-reconciliation/src/routes/ssr.tsx
  • e2e/react-start/session-handling/src/routes/server-functions.tsx
  • e2e/react-start/session-handling/src/routes/api/session-named.ts
  • e2e/react-start/response-reconciliation/playwright.config.ts
  • packages/start-server-core/tests/server-functions-handler.test.ts
  • packages/start-server-core/src/server-functions-handler.ts
  • packages/start-server-core/src/internal-request-response.ts
  • packages/start-server-core/src/createStartHandler.ts

📝 Walkthrough

Walkthrough

Updates Start runtime request/response reconciliation, server-function framing/error handling, server-entry error recovery, documentation, and adds two React E2E workspaces with Playwright tests covering response reconciliation and session handling.

Changes

Start runtime and E2E validation

Layer / File(s) Summary
Runtime reconciliation, headers, cookies, sessions
packages/start-server-core/src/..., packages/start-server-core/package.json, packages/start-server-core/vite.config.ts
Reimplement request/response state around AsyncLocalStorage, add header utilities, set-cookie parsing/merge semantics, response reconciliation helpers, session bridge to H3, and re-export surface changes.
Server-function framing and error serialization
packages/start-server-core/src/server-functions-handler.ts, packages/start-server-core/tests/*
Refactor server-function payload parsing, framed streaming serialization, protected serialized headers, and createServerFnErrorResponse plus tests for errors/streaming.
Start handler, middleware, redirects, and wrappers
packages/start-server-core/src/createStartHandler.ts, packages/react-start/src/default-entry/server.ts, packages/solid-start/src/default-entry/server.ts, packages/vue-start/src/default-entry/server.ts
Rewrite middleware execution, reconcile responses (including SSR stream ownership), centralize redirect serialization, and wrap framework server entries to route uncaught errors through handleStartError.
Docs, release metadata, and entry planning
docs/start/framework/react/guide/server-entry-point.md, packages/start-server-core/docs/RECONCILIATION.md, packages/start-server-core/skills/start-server-core/SKILL.md, packages/start-plugin-core/tests/entry-planning.test.ts, .changeset/start-response-state.md
Update server-entry docs to preserve opts/context, add reconciliation documentation, adjust skill docs, and add entry-planning tests and changeset metadata.
Response reconciliation E2E app & tests
e2e/react-start/response-reconciliation/*
New E2E workspace exercising middleware, route handlers, SSR loaders, server functions, redirects, and custom server-entry error handling; includes Playwright config, rsbuild/vite configs, and tests asserting reconciliation semantics for headers/status/cookies.
Session handling E2E app & tests
e2e/react-start/session-handling/*
New E2E workspace exercising cookie-backed and header-based sessions, middleware timing, SSR persistence, server-function session access, Playwright tests, and helper utilities.

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant ServerEntry
  participant StartHandler
  participant Middleware
  participant ServerFn

  Browser->>ServerEntry: HTTP request (headers/cookies)
  ServerEntry->>StartHandler: fetch(request, opts)
  StartHandler->>Middleware: run request middleware (before/after)
  Middleware-->>StartHandler: mutate StartEvent (status/headers/cookies)
  StartHandler->>ServerFn: invoke server-function (if applicable)
  ServerFn-->>StartHandler: serialized result or framed stream / error
  StartHandler->>StartHandler: reconcile helper-driven state with returned Response
  StartHandler-->>ServerEntry: final Response (status, headers, Set-Cookie, body)
  ServerEntry-->>Browser: send response
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • Sheraff
  • beaussan
  • SeanCassiere

🐰 I hopped through headers, cookies, and streams,
and patched the paths where reconciliation gleams.
When errors now fall, they land tidy and clear,
cookies and status keep the order we cheer.
Hop hop — the rabbit says: tests are here!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch response-reconciliation

@nx-cloud

nx-cloud Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 97e14ca

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ❌ Failed 9m 52s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 2m 13s View ↗

💡 Dealing with memory or CPU issues? See memory and CPU details with the resource usage add-on ↗.


☁️ Nx Cloud last updated this comment at 2026-06-08 22:26:06 UTC

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (8)
e2e/react-start/session-handling/src/session.ts (3)

23-31: ⚡ Quick win

Replace Record<string, any> with Record<string, unknown> for strict type safety.

Line 24 uses Record<string, any> which violates the TypeScript strict mode guideline.

♻️ Suggested fix
 export async function readSession(overrides: Record<string, unknown> = {}) {
-  const session = await useSession<Record<string, any>>(
+  const session = await useSession<Record<string, unknown>>(
     sessionConfig(overrides),
   )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/session-handling/src/session.ts` around lines 23 - 31, In
readSession, replace the loose generic Record<string, any> passed to useSession
with Record<string, unknown> to conform to strict TypeScript; update the
useSession<Record<string, unknown>>(...) type argument and ensure any downstream
accesses to session.data or session.id are typed/guarded appropriately (function
names: readSession and useSession, identifier: session.data/session.id).

Source: Coding guidelines


15-21: 💤 Low value

Consider logging or rethrowing JSON parsing errors.

Line 18 silently catches all errors and returns an empty object. While this provides resilience for E2E tests, it could mask malformed JSON or other issues during debugging.

💡 Optional improvement
 export async function readJson(request: Request) {
   try {
     return (await request.json()) as Record<string, unknown>
   } catch {
+    console.warn('Failed to parse request JSON, returning empty object')
     return {}
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/session-handling/src/session.ts` around lines 15 - 21, The
readJson function currently swallows all JSON parse errors and returns {}
silently; modify readJson to catch the error as a variable (e.g., catch (err))
and either log the error details (using console.error or the project logger)
before returning {} or rethrow the error so callers can handle it; optionally
add a boolean parameter like suppressErrors (default true) to preserve current
behavior in tests while allowing callers to surface errors when needed.

33-45: ⚡ Quick win

Replace Record<string, any> with Record<string, unknown> for strict type safety.

Line 37 uses Record<string, any> which violates the TypeScript strict mode guideline.

♻️ Suggested fix
 export async function updateSessionData(
   update: Record<string, unknown>,
   overrides: Record<string, unknown> = {},
 ) {
-  const session = await useSession<Record<string, any>>(
+  const session = await useSession<Record<string, unknown>>(
     sessionConfig(overrides),
   )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/session-handling/src/session.ts` around lines 33 - 45, The
function updateSessionData uses useSession with an explicit type Record<string,
any> which breaks strict TypeScript rules; change that generic to Record<string,
unknown> so the call becomes useSession<Record<string,
unknown>>(sessionConfig(overrides)), ensure any local references to session.data
or returned types are compatible with unknown (e.g., keep them typed as unknown
instead of any) and run type checks to fix any resulting type errors in
updateSessionData, useSession, or sessionConfig.

Source: Coding guidelines

e2e/react-start/session-handling/src/routes/server-functions.tsx (3)

16-22: ⚡ Quick win

Replace Record<string, any> with Record<string, unknown> for strict type safety.

Line 17 uses Record<string, any> which violates the TypeScript strict mode guideline.

♻️ Suggested fix
 const readSessionFn = createServerFn().handler(async () => {
-  const session = await useSession<Record<string, any>>(sessionConfig())
+  const session = await useSession<Record<string, unknown>>(sessionConfig())
   return {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/session-handling/src/routes/server-functions.tsx` around
lines 16 - 22, In readSessionFn replace the loose generic Record<string, any>
passed to useSession with Record<string, unknown> to satisfy strict TypeScript
rules; update any downstream usages in the handler that assume mutable or
specific typed values from session.data to use type-safe access or narrow/cast
as needed (refer to readSessionFn, useSession, and sessionConfig to locate the
call).

Source: Coding guidelines


7-14: ⚡ Quick win

Replace Record<string, any> with Record<string, unknown> for strict type safety.

Line 8 uses Record<string, any> which violates the TypeScript strict mode guideline. Consider using unknown instead of any to maintain type safety while preserving flexibility in this E2E test.

♻️ Suggested fix
 const updateSessionFn = createServerFn().handler(async () => {
-  const session = await useSession<Record<string, any>>(sessionConfig())
+  const session = await useSession<Record<string, unknown>>(sessionConfig())
   await session.update({ serverFn: 'updated' })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/session-handling/src/routes/server-functions.tsx` around
lines 7 - 14, Change the session generic from Record<string, any> to
Record<string, unknown> in the updateSessionFn handler: locate the
createServerFn().handler block where useSession<Record<string,
any>>(sessionConfig()) is called and update the generic to use Record<string,
unknown> (affecting updateSessionFn and its use of useSession/sessionConfig) to
enforce strict TypeScript safety without losing flexibility.

Source: Coding guidelines


41-70: ⚡ Quick win

Replace any types with stricter alternatives for type safety.

Line 46 uses any for function parameters and return type, which violates the TypeScript strict mode guideline.

♻️ Suggested fix
 function ServerFunctionButton({
   name,
   fn,
 }: {
   name: string
-  fn: (...args: Array<any>) => Promise<any>
+  fn: () => Promise<{ id: string; data: Record<string, unknown> }>
 }) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/session-handling/src/routes/server-functions.tsx` around
lines 41 - 70, The parameter types using any are too loose; change the fn prop
type from (...args: Array<any>) => Promise<any> to (...args: unknown[]) =>
Promise<unknown> (or an appropriate specific generic) and update the useServerFn
invocation to pass/declare that same generic so TypeScript infers safer types
for serverFn (e.g., useServerFn<typeof fn> or useServerFn<(...args: unknown[])
=> Promise<unknown>>(fn)); also update any local uses/annotations that relied on
any (for example ensure result state remains a string but document/convert
serverFn() output via JSON.stringify) to avoid Array<any> and any return types.

Source: Coding guidelines

e2e/react-start/session-handling/src/routes/ssr.tsx (1)

6-14: ⚡ Quick win

Replace Record<string, any> with Record<string, unknown> for strict type safety.

Line 7 uses Record<string, any> which violates the TypeScript strict mode guideline.

♻️ Suggested fix
 const loadSession = createServerFn().handler(async () => {
-  const session = await useSession<Record<string, any>>(sessionConfig())
+  const session = await useSession<Record<string, unknown>>(sessionConfig())
   const count = Number(session.data.ssrCount ?? 0) + 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/session-handling/src/routes/ssr.tsx` around lines 6 - 14, The
handler type for loadSession uses Record<string, any>; update it to use
Record<string, unknown> to satisfy strict TypeScript rules: locate the
createServerFn().handler callback where useSession<Record<string,
any>>(sessionConfig()) is called and change the generic to
useSession<Record<string, unknown>>(sessionConfig()), keeping the rest of the
logic (session.id, session.data, session.update) intact.

Source: Coding guidelines

e2e/react-start/session-handling/src/start.ts (1)

5-26: ⚡ Quick win

Replace Record<string, any> with Record<string, unknown> for strict type safety.

Lines 10 and 18 use Record<string, any> which violates the TypeScript strict mode guideline.

♻️ Suggested fix
     if (scenario === 'before') {
-      await updateSession<Record<string, any>>(sessionConfig(), {
+      await updateSession<Record<string, unknown>>(sessionConfig(), {
         middleware: 'before',
       })
       return next()
     }
 
     if (scenario === 'after') {
       const result = await next()
-      await updateSession<Record<string, any>>(sessionConfig(), {
+      await updateSession<Record<string, unknown>>(sessionConfig(), {
         middleware: 'after',
       })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/session-handling/src/start.ts` around lines 5 - 26, The two
updateSession calls inside the sessionMiddleware createMiddleware().server async
handler (the branches for scenario === 'before' and scenario === 'after') use
Record<string, any>; replace both type arguments with Record<string, unknown> to
comply with strict TypeScript typing (i.e., updateSession<Record<string,
unknown>>(sessionConfig(), { middleware: 'before' }) and similarly for the
'after' branch).

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e/react-start/response-reconciliation/.gitignore`:
- Around line 14-15: The .gitignore entry "/public/build# Sentry Config File" is
malformed so "/public/build" won't be ignored; split the line into a pure
pattern and a comment: replace the single malformed line with a standalone
pattern "/public/build" and a separate comment line "# Sentry Config File"
(i.e., update the entry in response-reconciliation/.gitignore that currently
reads "/public/build# Sentry Config File").

In `@e2e/react-start/response-reconciliation/package.json`:
- Line 28: Update the internal dependency declaration for
"`@tanstack/router-e2e-utils`" in package.json to use the workspace protocol
exactly as "workspace:*" instead of "workspace:^"; locate the dependency entry
for "`@tanstack/router-e2e-utils`" and replace its version specifier "workspace:^"
with "workspace:*", then reinstall/update the lockfile to ensure consistent
dependency resolution.

In `@e2e/react-start/response-reconciliation/src/routes/server-functions.tsx`:
- Around line 46-55: The replacementMiddleware is using an `as any` escape to
return a Response—remove the `as any` and either cast through unknown to the
declared server middleware result type or update
FunctionMiddlewareServerFnResult to include Response so the returned new
Response is representable; specifically modify the return cast in the anonymous
function created by createMiddleware({ type: 'function' }).server(...) (the
block that checks resultWithResponse.result instanceof Response and returns new
Response) to use an `unknown`-based cast or extend
FunctionMiddlewareServerFnResult to accept Response. Also change
ServerFunctionButton’s `fn` prop type from Array<any>/Promise<any> to a safer
signature like `(...args: Array<unknown>) => Promise<unknown>` so callers retain
the instanceof Response check without `any`.

In `@e2e/react-start/session-handling/package.json`:
- Line 28: The dependency entry for `@tanstack/router-e2e-utils` uses the wrong
workspace protocol ("workspace:^"); update the package.json dependency for
"`@tanstack/router-e2e-utils`" to use the workspace protocol exactly as
"workspace:*" so it follows the repo's internal dependency rule; locate the
dependency in e2e/react-start/session-handling/package.json and replace the
version specifier accordingly.

In `@e2e/react-start/session-handling/src/routes/api/session-seal.ts`:
- Line 10: The session is currently typed as Record<string, any> which disables
strict typing; define a concrete session type (e.g., interface SessionData { /*
fields used below */ }) or use unknown with runtime type checks, then replace
useSession<Record<string, any>>(config) with useSession<SessionData>(config) (or
useSession<unknown>(config) plus narrow at runtime) and update any code that
accesses session properties to conform to the new type (adjust property names on
the SessionData interface to match existing usage).

---

Nitpick comments:
In `@e2e/react-start/session-handling/src/routes/server-functions.tsx`:
- Around line 16-22: In readSessionFn replace the loose generic Record<string,
any> passed to useSession with Record<string, unknown> to satisfy strict
TypeScript rules; update any downstream usages in the handler that assume
mutable or specific typed values from session.data to use type-safe access or
narrow/cast as needed (refer to readSessionFn, useSession, and sessionConfig to
locate the call).
- Around line 7-14: Change the session generic from Record<string, any> to
Record<string, unknown> in the updateSessionFn handler: locate the
createServerFn().handler block where useSession<Record<string,
any>>(sessionConfig()) is called and update the generic to use Record<string,
unknown> (affecting updateSessionFn and its use of useSession/sessionConfig) to
enforce strict TypeScript safety without losing flexibility.
- Around line 41-70: The parameter types using any are too loose; change the fn
prop type from (...args: Array<any>) => Promise<any> to (...args: unknown[]) =>
Promise<unknown> (or an appropriate specific generic) and update the useServerFn
invocation to pass/declare that same generic so TypeScript infers safer types
for serverFn (e.g., useServerFn<typeof fn> or useServerFn<(...args: unknown[])
=> Promise<unknown>>(fn)); also update any local uses/annotations that relied on
any (for example ensure result state remains a string but document/convert
serverFn() output via JSON.stringify) to avoid Array<any> and any return types.

In `@e2e/react-start/session-handling/src/routes/ssr.tsx`:
- Around line 6-14: The handler type for loadSession uses Record<string, any>;
update it to use Record<string, unknown> to satisfy strict TypeScript rules:
locate the createServerFn().handler callback where useSession<Record<string,
any>>(sessionConfig()) is called and change the generic to
useSession<Record<string, unknown>>(sessionConfig()), keeping the rest of the
logic (session.id, session.data, session.update) intact.

In `@e2e/react-start/session-handling/src/session.ts`:
- Around line 23-31: In readSession, replace the loose generic Record<string,
any> passed to useSession with Record<string, unknown> to conform to strict
TypeScript; update the useSession<Record<string, unknown>>(...) type argument
and ensure any downstream accesses to session.data or session.id are
typed/guarded appropriately (function names: readSession and useSession,
identifier: session.data/session.id).
- Around line 15-21: The readJson function currently swallows all JSON parse
errors and returns {} silently; modify readJson to catch the error as a variable
(e.g., catch (err)) and either log the error details (using console.error or the
project logger) before returning {} or rethrow the error so callers can handle
it; optionally add a boolean parameter like suppressErrors (default true) to
preserve current behavior in tests while allowing callers to surface errors when
needed.
- Around line 33-45: The function updateSessionData uses useSession with an
explicit type Record<string, any> which breaks strict TypeScript rules; change
that generic to Record<string, unknown> so the call becomes
useSession<Record<string, unknown>>(sessionConfig(overrides)), ensure any local
references to session.data or returned types are compatible with unknown (e.g.,
keep them typed as unknown instead of any) and run type checks to fix any
resulting type errors in updateSessionData, useSession, or sessionConfig.

In `@e2e/react-start/session-handling/src/start.ts`:
- Around line 5-26: The two updateSession calls inside the sessionMiddleware
createMiddleware().server async handler (the branches for scenario === 'before'
and scenario === 'after') use Record<string, any>; replace both type arguments
with Record<string, unknown> to comply with strict TypeScript typing (i.e.,
updateSession<Record<string, unknown>>(sessionConfig(), { middleware: 'before'
}) and similarly for the 'after' branch).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23200d41-9026-4b02-bf02-6895e9cb3185

📥 Commits

Reviewing files that changed from the base of the PR and between 6f1daf5 and fe11405.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (86)
  • .changeset/start-response-state.md
  • docs/start/framework/react/guide/server-entry-point.md
  • e2e/react-start/response-reconciliation/.gitignore
  • e2e/react-start/response-reconciliation/.prettierignore
  • e2e/react-start/response-reconciliation/package.json
  • e2e/react-start/response-reconciliation/playwright.config.ts
  • e2e/react-start/response-reconciliation/rsbuild.config.ts
  • e2e/react-start/response-reconciliation/server.js
  • e2e/react-start/response-reconciliation/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/response-reconciliation/src/components/NotFound.tsx
  • e2e/react-start/response-reconciliation/src/routeTree.gen.ts
  • e2e/react-start/response-reconciliation/src/router.tsx
  • e2e/react-start/response-reconciliation/src/routes/__root.tsx
  • e2e/react-start/response-reconciliation/src/routes/api/-response.ts
  • e2e/react-start/response-reconciliation/src/routes/api/base.ts
  • e2e/react-start/response-reconciliation/src/routes/api/bulk-headers.ts
  • e2e/react-start/response-reconciliation/src/routes/api/clear-returned-headers.ts
  • e2e/react-start/response-reconciliation/src/routes/api/direct-mutation-visible.ts
  • e2e/react-start/response-reconciliation/src/routes/api/explicit-set-cookie-header.ts
  • e2e/react-start/response-reconciliation/src/routes/api/get-response-header-helper.ts
  • e2e/react-start/response-reconciliation/src/routes/api/get-response-headers-helper.ts
  • e2e/react-start/response-reconciliation/src/routes/api/multiple-cookies.ts
  • e2e/react-start/response-reconciliation/src/routes/api/null-body-status.ts
  • e2e/react-start/response-reconciliation/src/routes/api/readonly-after-next.ts
  • e2e/react-start/response-reconciliation/src/routes/api/redirect-with-cookies.ts
  • e2e/react-start/response-reconciliation/src/routes/api/remove-returned-header.ts
  • e2e/react-start/response-reconciliation/src/routes/api/replace-after-direct-mutation.ts
  • e2e/react-start/response-reconciliation/src/routes/api/replace-explicit-set-cookie.ts
  • e2e/react-start/response-reconciliation/src/routes/api/route-after-next.ts
  • e2e/react-start/response-reconciliation/src/routes/api/route-before-next.ts
  • e2e/react-start/response-reconciliation/src/routes/api/same-boundary-conflict.ts
  • e2e/react-start/response-reconciliation/src/routes/api/throw-after-status.ts
  • e2e/react-start/response-reconciliation/src/routes/api/two-returned-responses.ts
  • e2e/react-start/response-reconciliation/src/routes/index.tsx
  • e2e/react-start/response-reconciliation/src/routes/server-functions.tsx
  • e2e/react-start/response-reconciliation/src/routes/ssr.tsx
  • e2e/react-start/response-reconciliation/src/server-entry.ts
  • e2e/react-start/response-reconciliation/src/start.ts
  • e2e/react-start/response-reconciliation/src/styles/app.css
  • e2e/react-start/response-reconciliation/tests/response-reconciliation.spec.ts
  • e2e/react-start/response-reconciliation/tsconfig.json
  • e2e/react-start/response-reconciliation/vite.config.ts
  • e2e/react-start/session-handling/.gitignore
  • e2e/react-start/session-handling/.prettierignore
  • e2e/react-start/session-handling/package.json
  • e2e/react-start/session-handling/playwright.config.ts
  • e2e/react-start/session-handling/rsbuild.config.ts
  • e2e/react-start/session-handling/server.js
  • e2e/react-start/session-handling/src/components/DefaultCatchBoundary.tsx
  • e2e/react-start/session-handling/src/components/NotFound.tsx
  • e2e/react-start/session-handling/src/routeTree.gen.ts
  • e2e/react-start/session-handling/src/router.tsx
  • e2e/react-start/session-handling/src/routes/__root.tsx
  • e2e/react-start/session-handling/src/routes/api/session-clear.ts
  • e2e/react-start/session-handling/src/routes/api/session-cookie-disabled.ts
  • e2e/react-start/session-handling/src/routes/api/session-header.ts
  • e2e/react-start/session-handling/src/routes/api/session-helper-cookie.ts
  • e2e/react-start/session-handling/src/routes/api/session-middleware.ts
  • e2e/react-start/session-handling/src/routes/api/session-named.ts
  • e2e/react-start/session-handling/src/routes/api/session-seal.ts
  • e2e/react-start/session-handling/src/routes/api/session.ts
  • e2e/react-start/session-handling/src/routes/index.tsx
  • e2e/react-start/session-handling/src/routes/server-functions.tsx
  • e2e/react-start/session-handling/src/routes/ssr.tsx
  • e2e/react-start/session-handling/src/session.ts
  • e2e/react-start/session-handling/src/start.ts
  • e2e/react-start/session-handling/src/styles/app.css
  • e2e/react-start/session-handling/tests/session-handling.spec.ts
  • e2e/react-start/session-handling/tsconfig.json
  • e2e/react-start/session-handling/vite.config.ts
  • packages/react-start/src/default-entry/server.ts
  • packages/solid-start/src/default-entry/server.ts
  • packages/start-plugin-core/src/vite/plugin.ts
  • packages/start-plugin-core/tests/entry-planning.test.ts
  • packages/start-server-core/docs/RECONCILIATION.md
  • packages/start-server-core/package.json
  • packages/start-server-core/skills/start-server-core/SKILL.md
  • packages/start-server-core/src/createStartHandler.ts
  • packages/start-server-core/src/headers.ts
  • packages/start-server-core/src/internal-request-response.ts
  • packages/start-server-core/src/request-response.ts
  • packages/start-server-core/src/server-functions-handler.ts
  • packages/start-server-core/tests/createStartHandler.test.ts
  • packages/start-server-core/tests/server-functions-handler.test.ts
  • packages/start-server-core/vite.config.ts
  • packages/vue-start/src/default-entry/server.ts

Comment thread e2e/react-start/response-reconciliation/.gitignore
"@playwright/test": "^1.57.0",
"@rsbuild/core": "^2.0.8",
"@rsbuild/plugin-react": "^2.0.0",
"@tanstack/router-e2e-utils": "workspace:^",

@coderabbitai coderabbitai Bot Jun 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use workspace:* for internal dependency on Line 28.

@tanstack/router-e2e-utils is currently workspace:^; this conflicts with the repository rule for internal packages.

As per coding guidelines, "**/package.json: Use workspace protocol (workspace:*) for internal dependencies".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/response-reconciliation/package.json` at line 28, Update the
internal dependency declaration for "`@tanstack/router-e2e-utils`" in package.json
to use the workspace protocol exactly as "workspace:*" instead of "workspace:^";
locate the dependency entry for "`@tanstack/router-e2e-utils`" and replace its
version specifier "workspace:^" with "workspace:*", then reinstall/update the
lockfile to ensure consistent dependency resolution.

Source: Coding guidelines

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping workspace:^ — 37 of 40 e2e apps use it for @tanstack/router-e2e-utils; matching the dominant local convention.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread e2e/react-start/session-handling/package.json
Comment thread e2e/react-start/session-handling/src/routes/api/session-seal.ts
@schiller-manuel schiller-manuel force-pushed the response-reconciliation branch from fe11405 to 97e14ca Compare June 8, 2026 22:10
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

8 package(s) bumped directly, 8 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/react-router 1.170.15 → 1.170.16 Changeset
@tanstack/react-start 1.168.25 → 1.168.26 Changeset
@tanstack/solid-router 1.170.15 → 1.170.16 Changeset
@tanstack/solid-start 1.168.25 → 1.168.26 Changeset
@tanstack/start-client-core 1.170.12 → 1.170.13 Changeset
@tanstack/start-plugin-core 1.171.17 → 1.171.18 Changeset
@tanstack/start-server-core 1.169.14 → 1.169.15 Changeset
@tanstack/vue-start 1.168.24 → 1.168.25 Changeset
@tanstack/react-start-client 1.168.13 → 1.168.14 Dependent
@tanstack/react-start-rsc 0.1.24 → 0.1.25 Dependent
@tanstack/react-start-server 1.167.19 → 1.167.20 Dependent
@tanstack/solid-start-client 1.168.13 → 1.168.14 Dependent
@tanstack/solid-start-server 1.167.19 → 1.167.20 Dependent
@tanstack/start-static-server-functions 1.167.17 → 1.167.18 Dependent
@tanstack/vue-start-client 1.167.17 → 1.167.18 Dependent
@tanstack/vue-start-server 1.167.19 → 1.167.20 Dependent

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Benchmarks

  • Commit: dbe8fd89fb8d
  • Measured at: 2026-06-08T22:12:21.069Z
  • Baseline source: history:41e7a24f693b
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Initial gzip Raw Brotli Trend
react-router.minimal 87.25 KiB 0 B (0.00%) 87.11 KiB 273.59 KiB 75.96 KiB ██▂▂▂▂▂▂▁▃▃
react-router.full 90.98 KiB 0 B (0.00%) 90.84 KiB 285.48 KiB 79.13 KiB ██▇▇▇▇▇▇▁▁▁
solid-router.minimal 35.48 KiB 0 B (0.00%) 35.36 KiB 105.86 KiB 32.03 KiB ▃▃▆▆▆▆▆▆▆█▁
solid-router.full 40.53 KiB 0 B (0.00%) 40.41 KiB 121.08 KiB 36.46 KiB ██▄▄▄▄▄▄▄▅▁
vue-router.minimal 52.97 KiB 0 B (0.00%) 52.84 KiB 149.90 KiB 47.62 KiB ██▆▆▆▆▆▆▆▁▁
vue-router.full 58.96 KiB 0 B (0.00%) 58.83 KiB 168.66 KiB 52.87 KiB ██▁▁▁▁▁▁▁▅▅
react-start.minimal 101.88 KiB 0 B (0.00%) 101.75 KiB 321.90 KiB 88.14 KiB ▅▅▁▁▁▁▁▁▁██
react-start.deferred-hydration 102.62 KiB 0 B (0.00%) 101.77 KiB 323.28 KiB 88.85 KiB ▇▇▁▁▁▁▁▁▇██
react-start.full 105.27 KiB 0 B (0.00%) 105.13 KiB 331.84 KiB 91.03 KiB ▃▃▃▃████▁▁▁
react-start.rsbuild.minimal 99.59 KiB 0 B (0.00%) 99.42 KiB 316.35 KiB 85.75 KiB ▅▅██████▁▅▅
react-start.rsbuild.minimal-iife 99.99 KiB 0 B (0.00%) 99.83 KiB 317.29 KiB 86.07 KiB ▄▄██████▁▆▆
react-start.rsbuild.full 102.81 KiB 0 B (0.00%) 102.64 KiB 326.41 KiB 88.40 KiB ▄▄▅▅████▁▁▁
solid-start.minimal 49.60 KiB 0 B (0.00%) 49.47 KiB 151.93 KiB 43.79 KiB ▁▁▇▇▇▇▇▇▇█▂
solid-start.deferred-hydration 52.86 KiB 0 B (0.00%) 49.53 KiB 159.97 KiB 46.81 KiB ▁▁▆▆▆▆▆▆▆█▅
solid-start.full 55.40 KiB 0 B (0.00%) 55.27 KiB 168.97 KiB 48.80 KiB ▃▃▁▁▇▇▇▇▇█▆
vue-start.minimal 71.02 KiB 0 B (0.00%) 70.89 KiB 207.04 KiB 62.93 KiB ▂▂▁▁▁▁▁▁▁██
vue-start.full 75.01 KiB 0 B (0.00%) 74.88 KiB 219.68 KiB 66.40 KiB ▂▂▁▁▇▇▇▇▇██

Current gzip tracks all emitted client JS chunks. Initial gzip tracks only the entry/import graph. Trend sparkline is historical current gzip ending with this PR measurement; lower is better.

@pkg-pr-new

pkg-pr-new Bot commented Jun 8, 2026

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@7588

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/@tanstack/eslint-plugin-router@7588

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@7588

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@7588

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/@tanstack/nitro-v2-vite-plugin@7588

@tanstack/react-router

npm i https://pkg.pr.new/@tanstack/react-router@7588

@tanstack/react-router-devtools

npm i https://pkg.pr.new/@tanstack/react-router-devtools@7588

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/@tanstack/react-router-ssr-query@7588

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@7588

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@7588

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@7588

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@7588

@tanstack/router-cli

npm i https://pkg.pr.new/@tanstack/router-cli@7588

@tanstack/router-core

npm i https://pkg.pr.new/@tanstack/router-core@7588

@tanstack/router-devtools

npm i https://pkg.pr.new/@tanstack/router-devtools@7588

@tanstack/router-devtools-core

npm i https://pkg.pr.new/@tanstack/router-devtools-core@7588

@tanstack/router-generator

npm i https://pkg.pr.new/@tanstack/router-generator@7588

@tanstack/router-plugin

npm i https://pkg.pr.new/@tanstack/router-plugin@7588

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/@tanstack/router-ssr-query-core@7588

@tanstack/router-utils

npm i https://pkg.pr.new/@tanstack/router-utils@7588

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/@tanstack/router-vite-plugin@7588

@tanstack/solid-router

npm i https://pkg.pr.new/@tanstack/solid-router@7588

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/@tanstack/solid-router-devtools@7588

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/@tanstack/solid-router-ssr-query@7588

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@7588

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@7588

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@7588

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@7588

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@7588

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@7588

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@7588

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/@tanstack/start-static-server-functions@7588

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@7588

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@7588

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@7588

@tanstack/vue-router

npm i https://pkg.pr.new/@tanstack/vue-router@7588

@tanstack/vue-router-devtools

npm i https://pkg.pr.new/@tanstack/vue-router-devtools@7588

@tanstack/vue-router-ssr-query

npm i https://pkg.pr.new/@tanstack/vue-router-ssr-query@7588

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@7588

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@7588

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@7588

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@7588

commit: 97e14ca

@nx-cloud nx-cloud Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nx Cloud is proposing a fix for your failed CI:

We updated requestHandler in internal-request-response.ts to validate the URL pathname with decodeURIComponent after URL parsing, which restores the 400 Bad Request behaviour for malformed UTF-8 percent-encoded sequences (e.g. %E0%A4, %80, %FF) that was previously provided by H3Event's constructor. Without this fix, the WHATWG URL parser silently accepts those byte sequences and the request falls through to the router, resulting in a 404 instead of the expected 400.

Tip

We verified this fix by re-running @tanstack/vue-router:test:unit, tanstack-react-start-e2e-basic:test:e2e--vite-prerender.

diff --git a/packages/start-server-core/src/internal-request-response.ts b/packages/start-server-core/src/internal-request-response.ts
index ae8f1a6d..fc615534 100644
--- a/packages/start-server-core/src/internal-request-response.ts
+++ b/packages/start-server-core/src/internal-request-response.ts
@@ -674,6 +674,18 @@ export function requestHandler<TRegister = unknown>(
       throw error
     }
 
+    try {
+      decodeURIComponent(requestUrl.pathname)
+    } catch (error) {
+      if (error instanceof URIError) {
+        return new Response(null, {
+          status: 400,
+          statusText: 'Bad Request',
+        })
+      }
+      throw error
+    }
+
     const response = createStartResponse()
     const responseMeta = createResponseMeta()
     trackStartResponse(response, responseMeta)

Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally bI6l-OhXi

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

@codspeed-hq

codspeed-hq Bot commented Jun 8, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing response-reconciliation (97e14ca) with main (41e7a24)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (6f1daf5) during the generation of this report, so 41e7a24 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Comment on lines +22 to +32
export function applyHeaders(source: Headers, target: Headers): void {
for (const [name, value] of source) {
if (name !== 'set-cookie') {
target.set(name, value)
}
}
for (const cookie of getSetCookieValues(source)) {
target.append('set-cookie', cookie)
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory there are other headers than set-cookie that you are allowed to set multiple times (for example on a Response that would be Link, Vary, ...). I'm not sure this method is correctly handling that.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multi-value headers are actually handled losslessly here: iterating a Headers object yields repeated values as one comma-combined string ([...h] for two Link values gives '<a>, <b>'), and per RFC 9110 §5.3 that combined form is semantically equivalent — so set(name, combinedValue) preserves them. Set-Cookie is the one header where comma-joining is unsafe (Expires contains commas), which is why it's special-cased via getSetCookie(). Added a comment in code explaining this.

let sanitized = ''
for (const char of statusMessage) {
const code = char.charCodeAt(0)
if (code === 0x09 || (code >= 0x20 && code <= 0x7e)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more of a nitpick for my puny human mind: it's nice to leave comments on char code values to make it easier to know which char they are referring to.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — added char comments (tab / printable ASCII space–'~').

return fallbackStatusCode
}
const code = typeof statusCode === 'string' ? Number(statusCode) : statusCode
if (!Number.isInteger(code) || code < 200 || code > 599) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk if it matters here, but some services send some quite exotic status codes (https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)

  • shopify => 783 Unexpected Token
  • linkedin => 999 Request denied

maybe users of Start also want to do weird things like this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, but those exotic codes structurally can't flow through Start: the entire server pipeline (server routes, SSR documents, and server fns alike) uses Fetch Response objects as its currency, and the constructor throws outside 200-599:

new Response(null, { status: 999 })
// RangeError: init["status"] must be in the range of 200 to 599, inclusive.

So LinkedIn's 999 can't even be returned from a handler, let alone reconciled. The clamp just mirrors the platform constraint (and old h3-based behavior, which silently fell back too). New in this PR: setResponseStatus now warns in development when a value gets ignored, so this no longer fails silently. Also added a comment above sanitizeStatusCode documenting the constraint.

protectedHeaders?: ProtectedHeaders,
): void {
if (meta.clearHeaders) {
for (const name of Array.from(target.keys())) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

silly AI doesn't know javascript

Suggested change
for (const name of Array.from(target.keys())) {
for (const name of target.keys()) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Careful — this one is load-bearing. Headers iteration is live; deleting while iterating skips entries:

const h = new Headers()
h.set('a','1'); h.set('b','2'); h.set('c','3'); h.set('d','4')
for (const name of h.keys()) h.delete(name)
console.log([...h.keys()]) // [ 'b', 'd' ] — half survive!

The Array.from snapshot is required. Added a comment in code so this doesn't get "simplified" later.

const headers = getRequestHeaders()
if (opts?.xForwardedHost) {
const forwardedHost = headers.get('x-forwarded-host')
const host = forwardedHost?.split(',')[0]?.trim()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: if you know you only need the 1st segment, limit .split() to that segment

Suggested change
const host = forwardedHost?.split(',')[0]?.trim()
const host = forwardedHost?.split(',', 1)[0]?.trim()

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied — also to the same pattern in getRequestIP and getRequestProtocol.

@hokkyss

hokkyss commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Thanks for accomodating my request 🙏🙏

@hokkyss

hokkyss commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Use handleStartError(error) when you want custom top-level logic, such as logging or reporting, but still want Start to produce the same error response as the generated server entry.

handleStartError(error) is useful because the error is caught outside the active Start handler. It recovers the Start request state associated with non-primitive thrown errors and reconciles that state onto the error response. Primitive throws cannot carry that association.

If you do not want to preserve response helpers that ran before the error, do not call handleStartError. Return your own response instead:

I am curious, is it possible to customize the error response, but still using Start's reconciliation behavior?

@schiller-manuel

Copy link
Copy Markdown
Collaborator Author

@hokkyss can you explain what you want to do exactly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants