🙋 fix: Await ask_user_question Tool Factory in loadTools (#14291)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
GitNexus Index / index (push) Waiting to run
GitNexus Index / post-index (push) Blocked by required conditions

`loadTools` contracts every `requestedTools` entry as `() => Promise<Tool>`
and the loader relies on it via `validTool().catch(...)`.

The `ask_user_question` factory was registered synchronously, and
`createAskUserQuestionTool()` returns a `DynamicStructuredTool` directly, so
the call returned a tool with no `.catch`, throwing
`TypeError: validTool(...).catch is not a function`.

The throw happens inside the loop before `Promise.all`, so it aborted the
entire tool load for the turn, not just this tool: agents with
`ask_user_question` attached ran with no tools at all.
This commit is contained in:
Danny Avila 2026-07-15 13:41:55 -04:00 committed by GitHub
parent f1b9c5f091
commit abcbc40b8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 12 additions and 1 deletions

View file

@ -372,7 +372,7 @@ const loadTools = async ({
};
continue;
} else if (tool === ASK_USER_QUESTION_TOOL_NAME) {
requestedTools[tool] = () => createAskUserQuestionTool();
requestedTools[tool] = async () => createAskUserQuestionTool();
continue;
} else if (tool === SET_MEMORY_TOOL_NAME || tool === DELETE_MEMORY_TOOL_NAME) {
requestedTools[tool] = () =>

View file

@ -52,6 +52,7 @@ jest.mock('~/config', () => ({
const { Calculator } = require('@librechat/agents');
const { Constants } = require('librechat-data-provider');
const { ASK_USER_QUESTION_TOOL_NAME } = require('@librechat/api');
const { User } = require('~/db/models');
const PluginService = require('~/server/services/PluginService');
@ -304,6 +305,16 @@ describe('Tool Handlers', () => {
delete process.env.SD_WEBUI_URL;
});
it('loads the ask_user_question tool when not returning a map', async () => {
const { loadedTools } = await loadTools({
user: fakeUser._id,
tools: [ASK_USER_QUESTION_TOOL_NAME],
useSpecs: true,
});
expect(loadedTools).toHaveLength(1);
expect(loadedTools[0].name).toBe(ASK_USER_QUESTION_TOOL_NAME);
});
it('passes request body to chat MCP tool creation and skips stale cache for BODY-scoped servers', async () => {
const serverName = 'body-scoped';
const toolKey = `search${Constants.mcp_delimiter}${serverName}`;