Skip to content

fix(client): handle empty JSON response body without content-length#1998

Open
LeSingh1 wants to merge 1 commit into
openai:mainfrom
LeSingh1:fix-empty-json-body
Open

fix(client): handle empty JSON response body without content-length#1998
LeSingh1 wants to merge 1 commit into
openai:mainfrom
LeSingh1:fix-empty-json-body

Conversation

@LeSingh1

Copy link
Copy Markdown

Problem

defaultParseResponse (src/internal/parse.ts) already special-cases an empty JSON response body:

const contentLength = response.headers.get('content-length');
if (contentLength === '0') {
  // if there is no content we can't do anything
  return undefined as T;
}

const json = await response.json();

That guard only fires when the server sends an explicit content-length: 0 header. When the header is absent — which is common over HTTP/2, with chunked transfer encoding, or behind gateways/proxies and OpenAI-compatible servers — an empty body with a JSON content-type falls through to response.json() and throws:

SyntaxError: Unexpected end of JSON input

So two byte-for-byte identical empty responses parse differently depending solely on whether the server happened to include content-length: 0. The existing guard shows this case is meant to be handled gracefully; it's just incomplete.

Reproduction

const client = new OpenAI({
  apiKey: '...',
  // An empty body with a JSON content-type and no `content-length` header.
  // (`new Response('')` in undici sets no content-length.)
  fetch: async () => new Response('', { headers: { 'Content-Type': 'application/json' } }),
});

await client.request({ path: '/foo', method: 'get' });
// Before: throws `SyntaxError: Unexpected end of JSON input`
// After:  resolves to `undefined`

Fix

Read the body as text first and treat an empty payload the same as an explicit content-length: 0. Non-empty bodies are still parsed with JSON.parse, so malformed JSON continues to throw as before.

const bodyText = await response.text();
if (!bodyText) {
  return undefined as T;
}
const json = JSON.parse(bodyText);

Verification

Added a response parsing test group in tests/index.test.ts covering:

  • empty JSON body without content-length → resolves to undefined (the fixed case; fails on main with SyntaxError: Unexpected end of JSON input)
  • empty JSON body with content-length: 0 → resolves to undefined (existing behavior preserved)
  • non-empty JSON body → parsed correctly
  • malformed non-empty JSON body → still throws
$ npx jest tests/index.test.ts
Tests:       68 passed, 68 total

npx tsc --noEmit, eslint, and prettier --check all pass on the changed files.

`defaultParseResponse` already returns `undefined` for an empty JSON
response when the server sends `content-length: 0`, but when that header
is absent (e.g. over HTTP/2 or with chunked transfer encoding) an empty
body reached `response.json()` and threw `SyntaxError: Unexpected end of
JSON input`. Read the body as text first and treat an empty payload the
same as an explicit `content-length: 0`.
@LeSingh1 LeSingh1 requested a review from a team as a code owner July 14, 2026 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant