From 1934694fef474ea22ccac74e9d07c587366cf314 Mon Sep 17 00:00:00 2001 From: djk01281 Date: Sun, 22 Feb 2026 23:20:55 +0900 Subject: [PATCH 1/4] fix(start-server-core): preserve status code when handler throws after setResponseStatus --- .../start-server-core/src/request-response.ts | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/start-server-core/src/request-response.ts b/packages/start-server-core/src/request-response.ts index f8de03aa566..019b0f9f910 100644 --- a/packages/start-server-core/src/request-response.ts +++ b/packages/start-server-core/src/request-response.ts @@ -103,12 +103,34 @@ function attachResponseHeaders( event: H3Event, ): MaybePromise { if (isPromiseLike(value)) { - return value.then((resolved) => { - if (resolved instanceof Response) { - mergeEventResponseHeaders(resolved, event) - } - return resolved - }) + return value.then( + (resolved) => { + if (resolved instanceof Response) { + mergeEventResponseHeaders(resolved, event) + } + return resolved + }, + (error) => { + const eventStatus = event.res.status + if (eventStatus) { + const eventStatusText = event.res.statusText + if (error instanceof Response) { + return new Response(error.body, { + status: eventStatus, + statusText: eventStatusText || error.statusText, + headers: error.headers, + }) as T + } + const message = + error instanceof Error ? error.message : String(error) + return new Response(message, { + status: eventStatus, + statusText: eventStatusText || '', + }) as T + } + throw error + }, + ) } if (value instanceof Response) { From c7c5f1419579f7cfc583baf2044f70753b555117 Mon Sep 17 00:00:00 2001 From: djk01281 Date: Sun, 22 Feb 2026 23:20:59 +0900 Subject: [PATCH 2/4] test(start-server-core): add tests for requestHandler status preservation --- .../tests/request-response.test.ts | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 packages/start-server-core/tests/request-response.test.ts diff --git a/packages/start-server-core/tests/request-response.test.ts b/packages/start-server-core/tests/request-response.test.ts new file mode 100644 index 00000000000..6cd72526791 --- /dev/null +++ b/packages/start-server-core/tests/request-response.test.ts @@ -0,0 +1,88 @@ +import { describe, expect, it } from 'vitest' + +import { + getRequest, + requestHandler, + setResponseStatus, +} from '../src/request-response' + +describe('setResponseStatus + throw preserves status code', () => { + it('should preserve status code when handler throws an Error after setResponseStatus', async () => { + const handler = requestHandler(async (_request: Request) => { + setResponseStatus(401) + throw new Error('Unauthorized') + }) + + const request = new Request('http://localhost/test') + const response = await handler(request, {}) + + expect(response.status).toBe(401) + }) + + it('should preserve status code when handler throws a bare Response after setResponseStatus', async () => { + const handler = requestHandler(async (_request: Request) => { + setResponseStatus(429, 'Too Many Requests') + throw new Response('Rate limited') + }) + + const request = new Request('http://localhost/test') + const response = await handler(request, {}) + + expect(response.status).toBe(429) + }) + + it('should preserve status code when thrown Response already has same status', async () => { + const handler = requestHandler(async (_request: Request) => { + setResponseStatus(403) + throw new Response('Forbidden', { status: 403 }) + }) + + const request = new Request('http://localhost/test') + const response = await handler(request, {}) + + expect(response.status).toBe(403) + }) + + it('should transfer event status when handler returns non-Response value', async () => { + const handler = requestHandler(async (_request: Request) => { + setResponseStatus(204) + return null as any + }) + + const request = new Request('http://localhost/test') + const response = await handler(request, {}) + + expect(response.status).toBe(204) + }) + + it('should return 500 when handler throws without setResponseStatus', async () => { + const handler = requestHandler(async (_request: Request) => { + throw new Error('unexpected failure') + }) + + const request = new Request('http://localhost/test') + const response = await handler(request, {}) + + expect(response.status).toBe(500) + }) +}) + +describe('requestHandler basic behavior', () => { + it('should handle async handler returning a Response', async () => { + const handler = requestHandler(async (_request: Request) => { + return new Response('Hello', { status: 200 }) + }) + + const request = new Request('http://localhost/test') + const response = await handler(request, {}) + + expect(response.status).toBe(200) + expect(await response.text()).toBe('Hello') + }) + + it('should throw when accessing event outside requestHandler context', async () => { + expect(() => getRequest()).toThrow( + 'No StartEvent found in AsyncLocalStorage', + ) + }) +}) From 82215e5cabab9b8bdfae150e151677ede01f49fd Mon Sep 17 00:00:00 2001 From: djk01281 Date: Sat, 28 Feb 2026 15:03:45 +0900 Subject: [PATCH 3/4] test(start-server-core): assert error message body in status preservation test --- packages/start-server-core/tests/request-response.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/start-server-core/tests/request-response.test.ts b/packages/start-server-core/tests/request-response.test.ts index 6cd72526791..4a81ba0f2e6 100644 --- a/packages/start-server-core/tests/request-response.test.ts +++ b/packages/start-server-core/tests/request-response.test.ts @@ -17,6 +17,7 @@ describe('setResponseStatus + throw preserves status code', () => { const response = await handler(request, {}) expect(response.status).toBe(401) + expect(await response.text()).toBe('Unauthorized') }) it('should preserve status code when handler throws a bare Response after setResponseStatus', async () => { From 9825a287538ea4a164a05f3c01e3f397d1a7067a Mon Sep 17 00:00:00 2001 From: djk01281 Date: Sat, 28 Feb 2026 15:27:46 +0900 Subject: [PATCH 4/4] fix(start-server-core): merge event set-cookie headers in rejection path --- .../start-server-core/src/request-response.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/start-server-core/src/request-response.ts b/packages/start-server-core/src/request-response.ts index 019b0f9f910..ab2b129b9d5 100644 --- a/packages/start-server-core/src/request-response.ts +++ b/packages/start-server-core/src/request-response.ts @@ -114,19 +114,22 @@ function attachResponseHeaders( const eventStatus = event.res.status if (eventStatus) { const eventStatusText = event.res.statusText - if (error instanceof Response) { - return new Response(error.body, { - status: eventStatus, - statusText: eventStatusText || error.statusText, - headers: error.headers, - }) as T - } - const message = - error instanceof Error ? error.message : String(error) - return new Response(message, { - status: eventStatus, - statusText: eventStatusText || '', - }) as T + const response = + error instanceof Response + ? new Response(error.body, { + status: eventStatus, + statusText: eventStatusText || error.statusText, + headers: error.headers, + }) + : new Response( + error instanceof Error ? error.message : String(error), + { + status: eventStatus, + statusText: eventStatusText || '', + }, + ) + mergeEventResponseHeaders(response, event) + return response as T } throw error },