Skip to content

OpenAI Responses: tool loops intermittently 400 with "function_call ... provided without its required 'reasoning' item" #1212

Description

@ryanmr

TanStack AI version

@tanstack/ai@0.48.0, @tanstack/ai-openai@0.21.1

Framework/Library version

node@24.19.0, zod@4.4.3 — server-side only, no UI framework involved

Describe the bug and the steps to reproduce it

On the Responses API, a tool loop intermittently dies on the second turn with:

400 Item 'fc_04c5d379bb13126a006a8b1700e3ac87d19dae6257035321cf' of type 'function_call'
was provided without its required 'reasoning' item:
'rs_0bf74942a9c5d320006a8b0d57d26487d1981665bb013fd0f3'.

When a reasoning model emits a reasoning item alongside a function_call, the Responses API requires that reasoning item to be sent back with the function_call on the following turn. OpenAITextAdapter re-sends the function_call but not its paired reasoning item, so the request is rejected and the run ends with RUN_ERROR after the tools have already executed.

It is intermittent, which is what makes it easy to miss — it fails only on runs where the model actually produced a reasoning item. On gpt-5.6-luna with default effort I measured 4 failures in 6 consecutive runs of the script below, unchanged between runs. My first attempt at a minimal reproduction passed twice and I nearly concluded the trigger was something else.

Not specific to parallel tool calls, large tool outputs, system prompts, or the number of registered tools — I bisected all four away. One tool and a one-line result reproduces it.

Workaround: modelOptions: { reasoning: { effort: 'none' } }. That suppresses the reasoning item entirely, so there is nothing to round-trip and the loop runs. Chat Completions is not an alternative — it rejects function tools alongside reasoning outright, with "Function tools with reasoning_effort are not supported ... set reasoning_effort to 'none'".

This looks like the OpenAI counterpart of #340 ("Anthropic tool loops: thinking blocks missing on turn 2+"), which was fixed for Anthropic.

Your Minimal, Reproducible Example - (Sandbox Highly Recommended)

Run this a few times — expect roughly two failures in three. npm i @tanstack/ai @tanstack/ai-openai zod, set OPENAI_API_KEY, npx tsx repro.mts:

import { chat, maxIterations, toolDefinition } from '@tanstack/ai';
import { OpenAITextAdapter } from '@tanstack/ai-openai';
import { z } from 'zod';

const search = toolDefinition({
  name: 'search',
  description: 'Search passages of the archive.',
  inputSchema: z.object({ query: z.string() }),
  outputSchema: z.object({ passages: z.array(z.object({ id: z.string(), text: z.string() })) }),
}).server(({ query }) => ({ passages: [{ id: 'p1', text: `${query}: lorem ipsum dolor sit amet` }] }));

const stream = chat({
  adapter: new OpenAITextAdapter({ apiKey: process.env.OPENAI_API_KEY! }, 'gpt-5.6-luna'),
  messages: [{ role: 'user', content: 'Search the archive for anything about being tired, then summarise in one sentence.' }],
  tools: [search],
  agentLoopStrategy: maxIterations(6),
});

for await (const chunk of stream) {
  if (chunk.type === 'RUN_ERROR') console.log('RUN_ERROR:', chunk.message);
  if (chunk.type === 'TEXT_MESSAGE_CONTENT') process.stdout.write(chunk.delta);
}

Expected: the loop completes and the model answers using the tool result.
Actual: about two runs in three end in RUN_ERROR with the 400 above, after the tool has run.

Do you intend to try to help solve this bug with your own PR?

No

Metadata

Metadata

Assignees

No one assigned

    Labels

    has-prAn open PR references this issuewaiting-on: maintainerThe ball is in the maintainers’ court

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions