Skip to content

Any tool error crashes the whole run via unhandled promise rejection in langchain tool-call transformer #426

Description

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions