fix: use Promise.allSettled to prevent missing ToolMessage responses
Trigger auto deployment for soc-langgraph / build-and-deploy (push) Failing after 39s
Deploy LangGraph UI to Azure Static Web Apps / build-and-deploy (push) Failing after 47s

When LLM sends multiple parallel tool_calls but one throws unexpectedly,
Promise.all loses all results causing 400 error from OpenAI.
Promise.allSettled ensures every tool_call_id gets a response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gongzhiyong
2026-04-13 00:20:57 +08:00
co-authored by Claude Opus 4.6
parent e3147231a6
commit 9cf6293198
3 changed files with 50 additions and 6 deletions
@@ -144,8 +144,22 @@ export async function toolExecutorNode(
}
});
const results = await Promise.all(executions);
toolMessages.push(...results);
const settled = await Promise.allSettled(executions);
for (let i = 0; i < settled.length; i++) {
const result = settled[i];
if (result.status === "fulfilled") {
toolMessages.push(result.value);
} else {
const tc = toolCalls[i];
const errMsg = result.reason instanceof Error ? result.reason.message : String(result.reason);
console.error(`[tool-executor] Unhandled rejection for ${tc.name}:`, result.reason);
toolMessages.push({
role: "tool" as const,
tool_call_id: tc.id ?? "",
content: JSON.stringify({ ok: false, tool: tc.name, error: errMsg }),
});
}
}
return {
messages: toolMessages,
@@ -1033,8 +1033,22 @@ export async function toolExecutorNode(
}
});
const results = await Promise.all(executions);
toolMessages.push(...results);
const settled = await Promise.allSettled(executions);
for (let i = 0; i < settled.length; i++) {
const result = settled[i];
if (result.status === "fulfilled") {
toolMessages.push(result.value);
} else {
const tc = toolCalls[i];
const errMsg = result.reason instanceof Error ? result.reason.message : String(result.reason);
console.error(`[tool-executor] Unhandled rejection for ${tc.name}:`, result.reason);
toolMessages.push({
role: "tool" as const,
tool_call_id: tc.id ?? "",
content: JSON.stringify({ ok: false, tool: tc.name, error: errMsg }),
});
}
}
// Push next-actions card based on successful tool results
const successfulTools = statusList.filter(
@@ -375,8 +375,24 @@ export async function toolExecutorNode(
}
});
const results = await Promise.all(executions);
toolMessages.push(...results);
const settled = await Promise.allSettled(executions);
for (let i = 0; i < settled.length; i++) {
const result = settled[i];
if (result.status === "fulfilled") {
toolMessages.push(result.value);
} else {
// Ensure every tool_call_id gets a response even if execution threw unexpectedly
const tc = toolCalls[i];
const errMsg = result.reason instanceof Error ? result.reason.message : String(result.reason);
console.error(`[tool-executor] Unhandled rejection for ${tc.name}:`, result.reason);
toolMessages.push({
role: "tool" as const,
tool_call_id: tc.id ?? "",
content: JSON.stringify({ ok: false, tool: tc.name, error: errMsg }),
});
statusList.push({ tool: tc.name ?? "unknown", status: "error", message: errMsg });
}
}
return {
messages: toolMessages,