From b08c3aef5792b730119411f87256142814a58e99 Mon Sep 17 00:00:00 2001 From: Jakub Dobry Date: Tue, 3 Feb 2026 17:35:26 +0100 Subject: [PATCH 1/2] fix: JS should return an error if body is empty Fix error handling for API responses with empty body. Previously, when the API returned an error status code (4xx/5xx) with an empty response body, the error was not properly caught, causing a crash when accessing undefined response data. Now correctly detects errors by checking if `error !== undefined` instead of relying on truthiness check. --- .changeset/fix-empty-error-body-handling.md | 5 + packages/js-sdk/src/api/index.ts | 4 +- .../js-sdk/tests/api/handleApiError.test.ts | 100 ++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-empty-error-body-handling.md create mode 100644 packages/js-sdk/tests/api/handleApiError.test.ts diff --git a/.changeset/fix-empty-error-body-handling.md b/.changeset/fix-empty-error-body-handling.md new file mode 100644 index 0000000000..c57d629a39 --- /dev/null +++ b/.changeset/fix-empty-error-body-handling.md @@ -0,0 +1,5 @@ +--- +"e2b": patch +--- + +Fix error handling for API responses with empty body. diff --git a/packages/js-sdk/src/api/index.ts b/packages/js-sdk/src/api/index.ts index faa38cc0d4..0e7895df18 100644 --- a/packages/js-sdk/src/api/index.ts +++ b/packages/js-sdk/src/api/index.ts @@ -14,7 +14,9 @@ export function handleApiError( ) => Error = SandboxError, stackTrace?: string ): Error | undefined { - if (!response.error) { + // openapi-fetch returns empty string for error when response body is empty, + // so we check !== undefined instead of truthiness + if (response.error === undefined) { return } diff --git a/packages/js-sdk/tests/api/handleApiError.test.ts b/packages/js-sdk/tests/api/handleApiError.test.ts new file mode 100644 index 0000000000..4853d739b8 --- /dev/null +++ b/packages/js-sdk/tests/api/handleApiError.test.ts @@ -0,0 +1,100 @@ +import { assert, test, describe } from 'vitest' +import { handleApiError } from '../../src/api' +import { AuthenticationError, RateLimitError, SandboxError } from '../../src/errors' + +function createMockResponse( + status: number, + error: unknown, + data?: unknown +): { response: { status: number; ok: boolean }; error: unknown; data: unknown } { + return { + response: { status, ok: status >= 200 && status < 300 }, + error, + data, + } +} + +describe('handleApiError', () => { + describe('with empty error body', () => { + test('catches 404 with empty string error', () => { + const res = createMockResponse(404, '') + const err = handleApiError(res as any) + assert.instanceOf(err, SandboxError) + assert.include(err?.message, '404') + }) + + test('catches 400 with empty string error', () => { + const res = createMockResponse(400, '') + const err = handleApiError(res as any) + assert.instanceOf(err, SandboxError) + assert.include(err?.message, '400') + }) + + test('catches 500 with empty string error', () => { + const res = createMockResponse(500, '') + const err = handleApiError(res as any) + assert.instanceOf(err, SandboxError) + assert.include(err?.message, '500') + }) + }) + + describe('with JSON error body', () => { + test('catches 404 with message', () => { + const res = createMockResponse(404, { code: 404, message: 'Not found' }) + const err = handleApiError(res as any) + assert.instanceOf(err, SandboxError) + assert.include(err?.message, 'Not found') + }) + + test('catches 400 with message', () => { + const res = createMockResponse(400, { code: 400, message: 'Bad request' }) + const err = handleApiError(res as any) + assert.instanceOf(err, SandboxError) + assert.include(err?.message, 'Bad request') + }) + }) + + describe('special status codes', () => { + test('returns AuthenticationError for 401', () => { + const res = createMockResponse(401, { message: 'Invalid token' }) + const err = handleApiError(res as any) + assert.instanceOf(err, AuthenticationError) + assert.include(err?.message, 'Unauthorized') + }) + + test('returns AuthenticationError for 401 with empty body', () => { + const res = createMockResponse(401, '') + const err = handleApiError(res as any) + assert.instanceOf(err, AuthenticationError) + assert.include(err?.message, 'Unauthorized') + }) + + test('returns RateLimitError for 429', () => { + const res = createMockResponse(429, { message: 'Too many requests' }) + const err = handleApiError(res as any) + assert.instanceOf(err, RateLimitError) + assert.include(err?.message, 'Rate limit') + }) + + test('returns RateLimitError for 429 with empty body', () => { + const res = createMockResponse(429, '') + const err = handleApiError(res as any) + assert.instanceOf(err, RateLimitError) + assert.include(err?.message, 'Rate limit') + }) + }) + + describe('success responses', () => { + test('returns undefined for 200 success', () => { + const res = createMockResponse(200, undefined, { id: '123' }) + const err = handleApiError(res as any) + assert.isUndefined(err) + }) + + test('returns undefined for 201 success', () => { + const res = createMockResponse(201, undefined, { id: '123' }) + const err = handleApiError(res as any) + assert.isUndefined(err) + }) + }) +}) From 6aee2b8c426c305624d354672260e19d65fff09b Mon Sep 17 00:00:00 2001 From: Jakub Dobry Date: Tue, 3 Feb 2026 17:39:29 +0100 Subject: [PATCH 2/2] fix: format test file --- packages/js-sdk/tests/api/handleApiError.test.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/js-sdk/tests/api/handleApiError.test.ts b/packages/js-sdk/tests/api/handleApiError.test.ts index 4853d739b8..1dc1e855e2 100644 --- a/packages/js-sdk/tests/api/handleApiError.test.ts +++ b/packages/js-sdk/tests/api/handleApiError.test.ts @@ -1,12 +1,20 @@ import { assert, test, describe } from 'vitest' import { handleApiError } from '../../src/api' -import { AuthenticationError, RateLimitError, SandboxError } from '../../src/errors' +import { + AuthenticationError, + RateLimitError, + SandboxError, +} from '../../src/errors' function createMockResponse( status: number, error: unknown, data?: unknown -): { response: { status: number; ok: boolean }; error: unknown; data: unknown } { +): { + response: { status: number; ok: boolean } + error: unknown + data: unknown +} { return { response: { status, ok: status >= 200 && status < 300 }, error,