Skip to content

OpenAI integration (v11) breaks chat.completions.parse() / responses.parse(): "Body is unusable: Body has already been read" #24773

Description

@andres-dejesus

Is there an existing issue for this?

  • I have checked for existing issues

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node (also reproduced through @sentry/nextjs 11.0.0 with Turbopack)

SDK Version

11.0.0

Framework Version

openai 7.23.0, Node.js 24.20.0

Steps to Reproduce

With the default openAIIntegration active, client.chat.completions.parse() and client.responses.parse() always throw TypeError: Body is unusable: Body has already been read. create() works.

Self-contained repro (no network, fake fetch):

// repro.mjs — run with `node repro.mjs`
import * as Sentry from '@sentry/node';
Sentry.init({ dsn: 'https://public@o0.ingest.sentry.io/0', tracesSampleRate: 1.0 });
const { default: OpenAI } = await import('openai');

const body = JSON.stringify({
  id: 'x', object: 'chat.completion', created: 0, model: 'gpt-5-mini',
  choices: [{ index: 0, finish_reason: 'stop', message: { role: 'assistant', content: '{"a":1}', refusal: null } }],
});
const client = new OpenAI({
  apiKey: 'test',
  fetch: async () => new Response(body, { headers: { 'content-type': 'application/json' } }),
});
const params = {
  model: 'gpt-5-mini',
  messages: [{ role: 'user', content: 'hi' }],
  response_format: { type: 'json_schema', json_schema: { name: 'x', schema: { type: 'object' } } },
};

await Sentry.startSpan({ name: 'repro' }, async () => {
  try { await client.chat.completions.create(params); console.log('create ok'); }
  catch (e) { console.log('create FAIL:', e.message); }
  try { await client.chat.completions.parse(params); console.log('parse ok'); }
  catch (e) { console.log('parse FAIL:', e.message); }
});
Setup create() parse()
No Sentry ok ok
Sentry.init defaults ok FAIL: Body is unusable: Body has already been read
Sentry.init with integrations: (d) => d.filter((i) => i.name !== 'OpenAI') ok ok

client.responses.parse() fails the same way.

Expected Result

parse() resolves with the parsed completion, the same as without Sentry.

Actual Result

TypeError: Body is unusable: Body has already been read
    at consumeBody (node:internal/deps/undici/undici)
    at _Response.text (node:internal/deps/undici/undici)
    at defaultParseResponse (openai/internal/parse.mjs)
    at OpenAI.parseResponseWithTimeout (openai/client.mjs)
    ...

Analysis

The orchestrion wrapPromise transform wraps Completions.create / Responses.create. For non-native thenables it side-chains promise.then(...) and returns the original promise, so that APIPromise methods such as withResponse() stay available.

APIPromise parses lazily, though: calling .then() starts parse(), which reads response.text(). The SDK's parse() helpers are create(body)._thenUnwrap(transform), and in openai 7.x _thenUnwrap builds a new APIPromise over the same underlying Response, with its own parse step. Sentry's side-chained .then reads the body first, so the promise the caller actually awaits then reads a consumed body.

The same would apply to any other SDK helper built on _thenUnwrap over an instrumented create.

Workaround: await client.chat.completions.create(params) and pass the result to parseChatCompletion(completion, params) from openai/lib/parser (the function parse() uses internally). There is then only one APIPromise, so Sentry's .then and the caller share its cached parse. Disabling the OpenAI integration also works.

Additional Context

A fix probably needs the instrumentation to observe the result without triggering the APIPromise's parse, for example by hooking the parse result instead of calling .then() on the returned promise, or by also covering _thenUnwrap.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions