Skip to content

Commit 6975ce2

Browse files
committed
fix(files): answer 499 instead of 500 when a client disconnects mid-serve
The file serve route catches its own errors, so an abort thrown after the client went away was logged at error and answered 500, bypassing the 499 classification withRouteHandler already applies. Rethrow when the request signal has aborted, in the outer catch and in the cloud and local read helpers that log before rethrowing.
1 parent f542992 commit 6975ce2

2 files changed

Lines changed: 22 additions & 0 deletions

File tree

‎apps/sim/app/api/files/serve/[...path]/route.test.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
workspaceFileManagerMock,
3535
workspaceFileManagerMockFns,
3636
} from '@sim/testing/mocks/workspace-file-manager.mock'
37+
import { NextRequest } from 'next/server'
3738
import { beforeEach, describe, expect, it, vi } from 'vitest'
3839
import { MAX_BUFFERED_TRANSFER_BYTES } from '@/lib/uploads/shared/types'
3940

@@ -325,6 +326,24 @@ describe('File Serve API Route', () => {
325326
expect(serveLogger.error).not.toHaveBeenCalled()
326327
})
327328

329+
it('answers 499 without an error log when the client disconnects mid-read', async () => {
330+
const controller = new AbortController()
331+
mockReadLocalFileWithinLimit.mockImplementation(async () => {
332+
controller.abort()
333+
throw new Error('Request aborted')
334+
})
335+
336+
const response = await GET(
337+
new NextRequest('http://localhost:3000/api/files/serve/workspace/ws/report.md', {
338+
signal: controller.signal,
339+
}),
340+
createRouteContext({ path: ['workspace', 'ws', 'report.md'] })
341+
)
342+
343+
expect(response.status).toBe(499)
344+
expect(serveLogger.error).not.toHaveBeenCalled()
345+
})
346+
328347
describe('versioned cache lifetime', () => {
329348
const principal = createExecutorPrincipal({
330349
subjectUserId: 'test-user-id',

‎apps/sim/app/api/files/serve/[...path]/route.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ export const GET = withRouteHandler(
389389
request.method === 'HEAD'
390390
)
391391
} catch (error) {
392+
if (request.signal.aborted) throw error
392393
if (error instanceof InternalUnauthenticatedError) {
393394
logger.warn('Unauthorized file access attempt', { error: error.message })
394395
return NextResponse.json(
@@ -599,6 +600,7 @@ async function handleLocalFile(
599600
options.ifNoneMatch
600601
)
601602
} catch (error) {
603+
if (signal?.aborted) throw error
602604
logServeFailure('Error reading local file:', error)
603605
throw error
604606
}
@@ -689,6 +691,7 @@ async function handleCloudProxy(
689691
options.ifNoneMatch
690692
)
691693
} catch (error) {
694+
if (signal?.aborted) throw error
692695
logServeFailure('Error downloading from cloud storage:', error)
693696
throw error
694697
}

0 commit comments

Comments
 (0)