Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

openai-base: streamed `apply_patch` calls reach the app with an empty `diff`

オープン
#1,947 コメント 0 件 リアクション 0 件 担当者 1 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

@tombeckenham がすでに取り組んでいます。

2026年10月30日 から。

  • #1948 @peter-daptic による — オープン

評価

この issue はまだ評価されていません。

説明

has-pr waiting-on: maintainer
TanStack AI version

@tanstack/ai 0.68.0, @tanstack/ai-openai 0.29.1, @tanstack/openai-base 0.13.1. The same code is on main (@tanstack/openai-base 1.0.0, caaa97b).

Framework/Library version

Node 26.10, Next.js 16.3 (server-side chat()), openai 5.18

Describe the bug and the steps to reproduce it

When the Responses API streams an apply_patch_call, chat() runs the app's apply_patch tool with operation.diff === "". The real diff never reaches the app.

The streamed events for one patch look like this. They were captured from real responses from two models (gpt-5.6 and gpt-6.1), served by Azure OpenAI through a LiteLLM proxy. Both models produced the same sequence:

response.output_item.added      item: { type: "apply_patch_call", status: "in_progress",
                                        operation: { type: "update_file", path: "document.json", diff: "" } }
response.apply_patch_call_operation_diff.delta   "@@"
response.apply_patch_call_operation_diff.delta   "\n"
…
response.apply_patch_call_operation_diff.done
response.output_item.done       item: { type: "apply_patch_call", status: "completed",
                                        operation: { type: "update_file", path: "document.json",
                                                     diff: "@@\n-  \"title\": \"Draft memo\",\n+  \"title\": \"Final memo\",\n" } }
response.completed

processStreamChunks calls userToolChunks(item, …) on response.output_item.added (responses-text.ts#L2029). At that point the item's diff is still "". userToolChunks emits TOOL_CALL_START and TOOL_CALL_END with that input and marks the call ended (#L1598–L1614). When response.output_item.done arrives with the complete diff, the ?.ended check makes userToolChunks return nothing (#L2243). So does the response.completed fallback (#L2426).

In an agent loop this fails silently. The model keeps sending patches that never arrive, until the iteration limit stops the loop.

There's no way around streamed Responses for this tool: Chat Completions rejects it (Invalid value: 'apply_patch'. Supported values are: 'function' and 'custom'.), so apply_patch only works through the Responses API.

The other user-run tools are protected by accident. readUserExecutedCall returns null for a local_shell_call or shell_call until its command list is non-empty, so an incomplete item at output_item.added is skipped and picked up later at output_item.done. apply_patch_call only checks that operation.diff is a string, and "" passes.

The existing test, user-executed shell and apply_patch calls in packages/openai-base/tests/responses-text.test.ts, doesn't catch this. It only sends response.created and then response.completed, with the finished item, so the output_item.added path never runs.

Steps to reproduce

  1. Call chat() with an OpenAI Responses adapter and tools: [{ ...applyPatchTool(), execute }], where execute logs operation.diff and returns { status: "completed", output: "ok" }.
  2. Ask for a one-line edit to a file whose contents are in the prompt.
  3. execute logs "" every time. The model keeps calling apply_patch until maxIterations stops it.

Suggested fix

Handle user-run calls the way function_call is already handled. For a function call, output_item.added emits only TOOL_CALL_START, and TOOL_CALL_END (with the input) waits until the arguments are complete. User-run calls get both START and END from the first item seen.

The check is the item's own status. The apply_patch_call item arrives at output_item.added with status: "in_progress" and is "completed" at output_item.done and in response.completed. So userToolChunks can emit TOOL_CALL_START while the item is in_progress, and emit TOOL_CALL_END with the input once it isn't. This covers every user-run type (apply_patch_call, local_shell_call, shell_call, computer_call) and doesn't depend on each type's content looking incomplete.

The narrowest fix is to remove the userToolChunks call at L2029, since output_item.done and the response.completed fallback already emit the complete call.

Open question: should an item that ends "incomplete" (for example, the response hit max_output_tokens) be handed to the app at all? A truncated diff probably shouldn't be applied.

Workaround for anyone hitting this: wrap the OpenAI client's responses.create and drop response.output_item.added events whose item.type is apply_patch_call. The call is then emitted from output_item.done, with the full diff.

Your Minimal, Reproducible Example - (Sandbox Highly Recommended)

An E2E wire route that streams the sequence above to the adapter, with a spec that fails on main: api.openai-apply-patch-wire.ts and openai-apply-patch-wire.spec.ts. The PR adds the same sequence as a unit test in packages/openai-base/tests/responses-text.test.ts.

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

Yes, I am also opening a PR that solves the problem along side this issue

Terms & Code of Conduct
  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.
主要言語
TypeScript
スター
3.2k
フォーク
372
平均マージ
1日 5時間
マージ済み PR(30日)
233

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

TanStack/ai のほかの issue

TanStack/ai の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。