Summary
Any tool error during a personal/update agent run crashes the entire Node process with an unhandled promise rejection, instead of being surfaced to the model or handled by the run. This turns recoverable, model-facing errors (a hallucinated MCP tool name, a not-yet-configured connector, a transient LLM 5xx) into hard crashes that abort the whole run.
Observed crashes (all the same root cause)
Error: MCP tool notion-get-page-content was not returned by tools/list for notion. Run openwiki_list_mcp_tools first and use an exact discovered name.
at Object.process (.../langchain/dist/agents/transformers/tool-call.js:152:29)
at StreamMux.push (.../@langchain/langgraph/dist/stream/mux.js:173:66)
Same stack for Error: Gmail refresh token is required for OAuth refresh. (when the model calls an unconfigured connector) and for upstream 503s from a local OpenAI-compatible server.
Environment
- OpenWiki 0.2.1,
langchain 1.5.3 (unchanged since 0.1.2)
- Reproducible with any provider; easiest with a local model that occasionally guesses tool names.
Root cause
langchain/dist/agents/transformers/tool-call.js, createToolCallEntry() creates an output promise whose reject handler is called on tool-error:
const output = new Promise((res, rej) => {
resolveOutput = res;
rejectOutput = rej;
});
// ...
} else if (data.event === "tool-error") {
const message = data.message ?? "unknown error";
if (isToolInterrupt(message)) return true;
pending.rejectOutput(new Error(message)); // <-- rejects a promise nobody awaits
...
}
output is pushed onto a stream channel for consumers that may never await it. When nothing awaits it, rejectOutput(...) produces an unhandled rejection, which (under Node's default --unhandled-rejections=throw) terminates the process before the agent can route the error back to the model.
Reproduction
openwiki personal -p "Use openwiki_call_mcp_tool to call the notion tool named notion-get-page-content with args {}. Report what happens."
→ process exits 1 with the stack above, instead of the model receiving the "use an exact discovered name" message and retrying.
Suggested fix
Attach a no-op catch when the promise is created, so awaiting consumers still observe the rejection but an un-awaited one is not "unhandled":
const output = new Promise((res, rej) => {
resolveOutput = res;
rejectOutput = rej;
});
output.catch(() => {}); // rejection is still delivered to real awaiters
Verified locally: with this one line added to langchain 1.5.3's tool-call.js (the version both 0.1.2 and 0.2.1 depend on), a tool error that previously exited the process with the stack above instead flows back to the model and the run continues. On 0.2.1 a multi-turn personal run then proceeds through repeated tool-loop turns without the process dying.
Note: this file lives in the langchain dependency; if the fix belongs upstream in langchainjs, OpenWiki could alternatively guard its own tool funcs (see companion issue about connector tools returning errors as results).
Summary
Any tool error during a
personal/updateagent run crashes the entire Node process with an unhandled promise rejection, instead of being surfaced to the model or handled by the run. This turns recoverable, model-facing errors (a hallucinated MCP tool name, a not-yet-configured connector, a transient LLM 5xx) into hard crashes that abort the whole run.Observed crashes (all the same root cause)
Same stack for
Error: Gmail refresh token is required for OAuth refresh.(when the model calls an unconfigured connector) and for upstream503s from a local OpenAI-compatible server.Environment
langchain1.5.3 (unchanged since 0.1.2)Root cause
langchain/dist/agents/transformers/tool-call.js,createToolCallEntry()creates anoutputpromise whose reject handler is called ontool-error:outputis pushed onto a stream channel for consumers that may neverawaitit. When nothing awaits it,rejectOutput(...)produces an unhandled rejection, which (under Node's default--unhandled-rejections=throw) terminates the process before the agent can route the error back to the model.Reproduction
→ process exits 1 with the stack above, instead of the model receiving the "use an exact discovered name" message and retrying.
Suggested fix
Attach a no-op catch when the promise is created, so awaiting consumers still observe the rejection but an un-awaited one is not "unhandled":
Verified locally: with this one line added to
langchain1.5.3'stool-call.js(the version both 0.1.2 and 0.2.1 depend on), a tool error that previously exited the process with the stack above instead flows back to the model and the run continues. On 0.2.1 a multi-turnpersonalrun then proceeds through repeated tool-loop turns without the process dying.Note: this file lives in the
langchaindependency; if the fix belongs upstream in langchainjs, OpenWiki could alternatively guard its own tool funcs (see companion issue about connector tools returning errors as results).