{"record":{"id":"1995934286315384","repo":"can1357/oh-my-pi","slug":"tool-toolcall-name-not-found","errorCode":null,"errorMessage":"Tool ${toolCall.name} not found","messagePattern":"Tool (.+?) not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/agent-loop.ts","lineNumber":2253,"sourceCode":"\t\tif (intentTracing) {\n\t\t\tconst { intent, strippedArgs } = extractIntent(toolCall.arguments);\n\t\t\targsForExecution = strippedArgs;\n\t\t\tif (intent) {\n\t\t\t\ttoolCall.intent = intent;\n\t\t\t} else if (typeof tool?.intent === \"function\") {\n\t\t\t\ttry {\n\t\t\t\t\tconst derived = tool.intent(strippedArgs as never)?.trim();\n\t\t\t\t\tif (derived) {\n\t\t\t\t\t\ttoolCall.intent = derived;\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// intent function must never break tool execution\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst validate = (args: Record<string, unknown>): Record<string, unknown> | undefined => {\n\t\t\ttry {\n\t\t\t\tif (!tool) throw new Error(`Tool ${toolCall.name} not found`);\n\t\t\t\treturn validateToolArguments(tool, { ...toolCall, arguments: args });\n\t\t\t} catch (validationError) {\n\t\t\t\tif (tool?.lenientArgValidation) {\n\t\t\t\t\tconst fallback = { ...args };\n\t\t\t\t\tdelete fallback.__parseError;\n\t\t\t\t\tdelete fallback.__rawJson;\n\t\t\t\t\treturn fallback;\n\t\t\t\t}\n\t\t\t\tentry.args = \"__parseError\" in args ? { __parseError: args.__parseError } : args;\n\t\t\t\tentry.validationErrorMessage =\n\t\t\t\t\tvalidationError instanceof Error ? validationError.message : String(validationError);\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t};\n\t\tconst effectiveArgs = validate(argsForExecution);\n\t\tif (effectiveArgs === undefined) continue;\n\t\tentry.args = effectiveArgs;\n\t\tif (!beforeToolCall || !tool) continue;","sourceCodeStart":2235,"sourceCodeEnd":2271,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/agent/src/agent-loop.ts#L2235-L2271","documentation":"During argument validation of an assistant tool call, the resolved `tool` variable is undefined — the model emitted a toolCall for a name that is not registered on the agent/session. The validate() helper throws this error synchronously; with lenientArgValidation tools it is caught and degraded to a parse-error result, but for normal tools it surfaces. It exists so unregistered tool names fail explicitly instead of crashing on `tool.parameters` lookups.","triggerScenarios":"The message content contains a toolCall whose `name` does not match any tool in the session's tool registry, and validation of its arguments is attempted (e.g. during transcript repair / streaming argument validation where validate() runs on partially or fully parsed args).","commonSituations":"Model hallucinating a tool name not in the current toolset; tools removed or filtered (dialect/permission gating) between the call being emitted and executed; session resumed with a different tool configuration than when the call was recorded; typo in tool registration vs. prompt reference.","solutions":["Log/inspect the offending toolCall.name and ensure a tool with that exact name is registered on the session before prompting","If tools are conditionally filtered, keep previously-offered tools registered until their pending calls complete (or return a proper tool-result error instead of dropping the tool)","For a resumed session, restore the same toolset that produced the recorded tool calls","Mark the tool lenientArgValidation only if you intend graceful degradation — it does not fix a missing tool registration"],"exampleFix":"// before\ndefineTool(agent, { name: \"read-file\", ... }); // registered with a dash\n// model calls \"read_file\" -> Tool read_file not found\n// after\nconst tool = defineTool({ name: \"read_file\", ... });\nagent.addTool(tool); // name matches what the prompt/model uses","handlingStrategy":"validation","validationCode":"// Ensure every tool name the model can see is registered\nfor (const call of assistantMessage.content.filter(c => c.type === \"toolCall\")) {\n  if (!session.getTools().some(t => t.name === call.name)) {\n    console.warn(`model referenced unregistered tool: ${call.name}`);\n  }\n}","typeGuard":"function isToolNotFoundError(err: unknown, name?: string): err is Error {\n  return err instanceof Error && err.message.startsWith(\"Tool \") && err.message.endsWith(\"not found\")\n    && (name === undefined || err.message.includes(name));\n}","tryCatchPattern":"try {\n  const args = validateToolArguments(tool, toolCall);\n} catch (err) {\n  if (isToolNotFoundError(err)) {\n    return createErrorToolResult(toolCall, `Unknown tool: ${toolCall.name}`); // let the model recover\n  }\n  throw err;\n}","preventionTips":["Register tools before prompting and keep names consistent between prompts and registration","When filtering tools by permissions/dialect, finish or cancel pending calls first","On session resume, restore the same toolset that produced the recorded calls","Normalize tool names (underscores vs dashes, case) if the provider mangles them"],"tags":["tool-calling","tool-registry","agent-loop","validation"],"backgroundTag":"unknown-tool-name","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}