{"record":{"id":"bdec4875279d3fb6","repo":"moeru-ai/airi","slug":"tamagotchi-extension-tool-not-found-key","errorCode":null,"errorMessage":"Tamagotchi extension tool not found: ${key}","messagePattern":"Tamagotchi extension tool not found: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-sdk-tamagotchi/src/tools/registry.ts","lineNumber":233,"sourceCode":"      items.push({\n        ownerExtensionId: record.ownerExtensionId,\n        name: record.tool.id,\n        description: record.tool.description,\n        parameters: structuredClone(record.tool.parameters),\n      })\n    }\n\n    return {\n      prompts: await this.listToolsetPrompts(),\n      tools: items,\n    }\n  }\n\n  async invoke(ownerExtensionId: string, toolId: string, input: unknown) {\n    const key = `${ownerExtensionId}:${toolId}`\n    const record = this.tools.get(key)\n    if (!record) {\n      throw new Error(`Tamagotchi extension tool not found: ${key}`)\n    }\n\n    return await record.execute(input)\n  }\n}\n","sourceCodeStart":215,"sourceCodeEnd":239,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/plugin-sdk-tamagotchi/src/tools/registry.ts#L215-L239","documentation":"Thrown by TamagotchiToolRegistry.invoke() when no tool record exists for the composite key `${ownerExtensionId}:${toolId}`. The registry is an in-memory Map keyed by that composite, so the invoke cannot route to an execute handler.","triggerScenarios":"Calling registry.invoke(ownerExtensionId, toolId, input) where the tool was never registered, was unregistered via unregister/unregisterOwnerSession/unregisterOwnerScope/clear, or where the ownerExtensionId or toolId does not exactly match the values used at registration time.","commonSituations":"Invoker passes the wrong extension id (e.g. module id instead of extension id), a tool was unregistered during session teardown but an in-flight xsai call still references it, or a tool id typo/mismatch between registration and invocation. Also occurs when a tool's availability gate filtered it out but the caller invokes directly bypassing listAvailableDescriptors.","solutions":["Verify the ownerExtensionId and toolId match exactly what was passed to registry.register().","Call registry.listAvailableDescriptors() or check registry before invoke to confirm the tool exists and is available.","Ensure invoke is not called after unregisterOwnerSession/clear/teardown has removed the tool.","Coordinate xsai tool dispatch to skip tools that were withdrawn between listing and invocation."],"exampleFix":"// before\nawait registry.invoke(extId, 'search', input) // throws if not registered under extId:search\n\n// after — guard before invoke\nconst descriptors = await registry.listAvailableDescriptors()\nconst exists = descriptors.some(d => d.id === 'search')\nif (!exists) throw new Error(`Tool 'search' is not available`)\nawait registry.invoke(extId, 'search', input)","handlingStrategy":"validation","validationCode":"const descriptors = await registry.listAvailableDescriptors()\nconst exists = descriptors.some(d => d.id === toolId)\nif (!exists) {\n  throw new Error(`Tool '${toolId}' is not registered or available for extension '${ownerExtensionId}'`)\n}\nreturn registry.invoke(ownerExtensionId, toolId, input)","typeGuard":"async function isToolRegistered(registry: TamagotchiToolRegistry, ownerExtensionId: string, toolId: string): Promise<boolean> {\n  const descriptors = await registry.listAvailableDescriptors()\n  return descriptors.some(d => d.id === toolId)\n}","tryCatchPattern":"try {\n  return await registry.invoke(ownerExtensionId, toolId, input)\n} catch (error) {\n  if (error instanceof Error && /Tamagotchi extension tool not found/.test(error.message)) {\n    // tool was never registered or was withdrawn; return a not-found response\n    return { error: 'tool-not-found', toolId, ownerExtensionId }\n  }\n  throw error\n}","preventionTips":["Snapshot the available tool list via listAvailableDescriptors() before dispatching xsai invocations.","Ensure invoke is not called after unregisterOwnerSession/clear during teardown.","Use the exact ownerExtensionId and toolId strings used at registration time."],"tags":["registry","tool-invocation","not-found","lifecycle"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}