Approval resume validates null-widened history args, so approved tools fail with repairToolInput: false
Maintainer antworten meist innerhalb von 1 Tag
@tombeckenham arbeitet bereits daran.
Seit 06.11.2026.
Bewertung
Dieses Issue wurde noch nicht bewertet.
Beschreibung
What happens
With the OpenAI adapter, an optional tool field comes back from the provider as null (strict-mode null widening). The live tool path undoes that before it validates. The approval resume path does not. It validates the arguments from the message history as they are.
So with repairToolInput: false, an approved tool fails its own input check on resume:
Input validation failed for tool bookFlight: #/date: Instance type "null" is invalid. Expected "string"., #: Property "date" does not match additional properties schema., #/date: False boolean schema.
Received arguments: {"destination":"Tokyo","date":null}
With the default repairToolInput it passes, because the repair step happens to coerce the null away.
The two events for one tool call also disagree:
TOOL_CALL_ARGSdeltas, joined:{"destination":"Tokyo","date":null}TOOL_CALL_END.input:{"destination":"Tokyo"}
An AG-UI client rebuilds the assistant tool call from the TOOL_CALL_ARGS deltas. That is the history it sends back on resume.
Expected
The resume path undoes null widening on the history arguments the same way the live path does, whatever repairToolInput is set to. Or TOOL_CALL_ARGS streams the same arguments that TOOL_CALL_END.input reports.
Repro
@tanstack/ai 1.2.0, @tanstack/ai-openai 1.1.1, Node 22, OPENAI_API_KEY set.
import { chat, toolDefinition, maxIterations } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
const bookFlight = toolDefinition({
name: "bookFlight",
description: "Book a flight",
inputSchema: {
type: "object",
properties: { destination: { type: "string" }, date: { type: "string" } },
required: ["destination"],
additionalProperties: false,
},
needsApproval: true,
}).server(async () => ({ booked: true }));
const threadId = `repro-${Date.now()}`;
const user = { role: "user", content: "Book a flight to Tokyo. I have no date yet." };
// Run 1: pauses for approval.
const run1 = [];
for await (const chunk of chat({
adapter: openaiText("gpt-4o-mini"),
messages: [user],
tools: [bookFlight],
threadId,
runId: "r1",
})) run1.push(chunk);
const { toolCallId } = run1.find((e) => e.type === "TOOL_CALL_START");
const streamedArgs = run1
.filter((e) => e.type === "TOOL_CALL_ARGS" && e.toolCallId === toolCallId)
.map((e) => e.delta)
.join(""); // {"destination":"Tokyo","date":null}
// Run 2: approve, with the history a client rebuilt from the stream.
for await (const chunk of chat({
adapter: openaiText("gpt-4o-mini"),
messages: [
user,
{
role: "assistant",
content: "",
toolCalls: [
{ id: toolCallId, type: "function", function: { name: "bookFlight", arguments: streamedArgs } },
],
},
],
tools: [bookFlight],
threadId,
runId: "r2",
parentRunId: "r1",
resume: [{ interruptId: `approval_${toolCallId}`, status: "resolved", payload: { approved: true } }],
repairToolInput: false,
agentLoopStrategy: maxIterations(3),
})) {
if (chunk.type === "TOOL_CALL_RESULT") console.log(chunk.content); // input validation error
}
Results:
| Run 2 history args | repairToolInput |
Result |
|---|---|---|
from TOOL_CALL_ARGS deltas |
false |
input validation error, tool does not run |
from TOOL_CALL_ARGS deltas |
default | tool runs |
from TOOL_CALL_END.input |
false |
tool runs |
Where it shows up
CopilotKit's built-in agent runs on chat() and sets repairToolInput: false, so tool argument errors go back to the model. Every approved tool with an optional field fails on resume there.
- Vorherrschende Sprache
- TypeScript
- Sterne
- 3.3k
- Forks
- 386
- Ø Merge
- 1 T. 6 Std.
- Gemergte PRs (30 T.)
- 221
Entwicklungsumgebung
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus TanStack/ai
-
openai-base: a Responses max_output_tokens cutoff ends as RUN_ERROR without usage instead of finishReason 'length'Evtl. vergeben @tombeckenham hat das vor 11 Tagen übernommen. Offenhas-pr waiting-on: maintainer
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 74/100
TanStack/ai#1916 · 1 Kommentar · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
-
Generate provider HTTP clients from modelschemas with KubbEvtl. vergeben @AlemTuzlak hat das heute übernommen. Offenhas-pr waiting-on: maintainer
TanStack/ai#2104 · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
-
chat(): a consumer that stops iterating (break / return()) runs no terminal middleware hookEvtl. vergeben @AlemTuzlak hat das vor 1 Tag übernommen. Offenhas-pr waiting-on: maintainer
TanStack/ai#2097 · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
-
openai-base: a caller abort from the openai client is not classified as an abort (APIUserAbortError has no name)Evtl. vergeben @tombeckenham hat das vor 1 Tag übernommen. Offenhas-pr waiting-on: maintainer
TanStack/ai#2096 · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
-
openai-base: a caller abort mid-stream logs 'processStreamChunks fatal' on the errors channelEvtl. vergeben @tombeckenham hat das vor 1 Tag übernommen. Offenhas-pr waiting-on: maintainer
TanStack/ai#2092 · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 1 Tag
Ähnliche Issues
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
prime-radiant-inc/evener#5901 ·
Maintainer antworten meist innerhalb von 1 Tag
-
bug needs-triage
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 62/100
Maintainer antworten meist innerhalb von 1 Tag
-
Bug: Batcher binds the texture that opens a new batch twice (one draw call per element with maxBatchableTextures = 1)Evtl. vergeben @darzarzycki hat das heute übernommen. Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 77/100
Maintainer antworten meist innerhalb von 2 Tagen
-
area:study-data p2-high ready-for-agent type:chore
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
fderuiter/deruiter.dev#3706 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 62/100
nicobailon/pi-subagents#3242 ·
Maintainer antworten meist innerhalb von 1 Tag