{"record":{"id":"0fef1db169353ed5","repo":"n8n-io/n8n","slug":"tool-this-name-requires-a-handler","errorCode":null,"errorMessage":"Tool \"${this.name}\" requires a handler","messagePattern":"Tool \"(.+?)\" requires a handler","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/agents/src/sdk/tool.ts","lineNumber":367,"sourceCode":"\n\t/**\n\t * Validate configuration and produce a `BuiltTool`.\n\t *\n\t * @throws if name, description, input schema, or handler is missing.\n\t * @throws if suspend is declared without resume or vice versa.\n\t */\n\tbuild(): BuiltTool {\n\t\tif (!this.name) {\n\t\t\tthrow new Error('Tool name is required');\n\t\t}\n\t\tif (!this.desc) {\n\t\t\tthrow new Error(`Tool \"${this.name}\" requires a description`);\n\t\t}\n\t\tif (!this.inputSchema) {\n\t\t\tthrow new Error(`Tool \"${this.name}\" requires an input schema`);\n\t\t}\n\t\tif (!this.handlerFn) {\n\t\t\tthrow new Error(`Tool \"${this.name}\" requires a handler`);\n\t\t}\n\n\t\tconst hasSuspend = this.suspendSchemaValue !== undefined;\n\t\tconst hasResume = this.resumeSchemaValue !== undefined;\n\n\t\tif (hasSuspend && !hasResume) {\n\t\t\tthrow new Error(`Tool \"${this.name}\" has .suspend() but missing .resume()`);\n\t\t}\n\t\tif (hasResume && !hasSuspend) {\n\t\t\tthrow new Error(`Tool \"${this.name}\" has .resume() but missing .suspend()`);\n\t\t}\n\n\t\tconst hasApproval =\n\t\t\t(this.requireApprovalValue ?? false) || this.needsApprovalFnValue !== undefined;\n\t\tif (hasApproval && (hasSuspend || hasResume)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Tool \"${this.name}\" cannot use both approval (.requireApproval/.needsApprovalFn) and suspend/resume (.suspend/.resume)`,\n\t\t\t);","sourceCodeStart":349,"sourceCodeEnd":385,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/agents/src/sdk/tool.ts#L349-L385","documentation":"Tool.build() requires a handler function — the code that actually executes when the LLM invokes the tool. Without it the tool is metadata-only and cannot run. Set it via .handler(async (input, ctx) => {...}). A tool with a name, description, and input schema but no handler is incomplete and rejected at build.","triggerScenarios":"Constructing a Tool, setting description and input, but forgetting .handler(...), then building. Also possible if .handler() is called with undefined due to an unfilled variable.","commonSituations":"Building a tool descriptor for inspection but accidentally passing it to an agent; refactoring a handler out into a variable that ends up undefined; splitting tool definition from handler attachment and missing the second step.","solutions":["Add .handler(async (input, ctx) => { ... }) to the builder chain.","If the tool is metadata-only (e.g. for the JSON-config flow), call .describe() instead of letting it reach .build() via an agent.","When attaching handlers conditionally, assert the handler reference is a function before calling .handler()."],"exampleFix":"// before\nconst t = new Tool('echo')\n  .description('Echo input')\n  .input(z.object({ msg: z.string() }));\nagent.tool(t); // throws: requires a handler\n\n// after\nconst t = new Tool('echo')\n  .description('Echo input')\n  .input(z.object({ msg: z.string() }))\n  .handler(async ({ msg }) => ({ echo: msg }));","handlingStrategy":"type-guard","validationCode":"function attachHandler(tool: Tool, fn: unknown) {\n  if (typeof fn !== 'function') {\n    throw new Error('Tool handler must be a function');\n  }\n  return tool.handler(fn as any);\n}","typeGuard":"function isHandler(fn: unknown): fn is (...args: any[]) => Promise<unknown> {\n  return typeof fn === 'function';\n}","tryCatchPattern":null,"preventionTips":["Keep handler attachment adjacent to the tool definition in source so it isn't dropped during refactor.","If splitting definition from handler, use a checklist or test that exercises agent.tool(...) to surface missing handlers early.","For descriptor-only flows, call .describe() not .build(); for executable tools, always chain .handler()."],"tags":["tool","builder","validation","handler"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}