{"record":{"id":"f0eaead6484ea062","repo":"mastra-ai/mastra","slug":"plugin-tool-toolname-is-no-longer-available","errorCode":null,"errorMessage":"Plugin tool \"${toolName}\" is no longer available","messagePattern":"Plugin tool \"(.+?)\" is no longer available","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"mastracode/sdk/src/plugins/manager.ts","lineNumber":231,"sourceCode":"      }\n    }\n\n    for (const [name, tool] of Object.entries(nextTools)) {\n      this.rawPluginTools[name] = tool;\n      if (!this.pluginTools[name]) {\n        this.pluginTools[name] = this.createLiveToolProxy(name);\n      }\n      this.syncLiveToolProxy(name, tool);\n    }\n  }\n\n  private createLiveToolProxy(toolName: string) {\n    return {\n      execute: async (...args: any[]) => {\n        await this.reloadChangedLocalPlugins();\n        const latestTool = this.rawPluginTools[toolName];\n        if (!latestTool?.execute) {\n          throw new Error(`Plugin tool \"${toolName}\" is no longer available`);\n        }\n        return (latestTool.execute as (...args: any[]) => unknown)(...args);\n      },\n    } as LoadedPlugin['tools'][string];\n  }\n\n  private syncLiveToolProxy(toolName: string, tool: LoadedPlugin['tools'][string]): void {\n    const proxy = this.pluginTools[toolName];\n    if (!proxy) return;\n    const mutableProxy = proxy as unknown as Record<string, unknown>;\n    for (const key of Object.keys(mutableProxy)) {\n      delete mutableProxy[key];\n    }\n    Object.assign(proxy, tool);\n    proxy.execute = this.createLiveToolProxy(toolName).execute;\n  }\n\n  private async reloadChangedLocalPlugins(): Promise<void> {","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/sdk/src/plugins/manager.ts#L213-L249","documentation":"Live plugin tools are proxied: on each execute() the manager reloads changed local plugins and looks the tool up by name in the latest rawPluginTools map. If the tool no longer exists (or lost its execute) after a reload, the proxy throws 'Plugin tool \"<name>\" is no longer available' instead of silently calling a stale implementation.","triggerScenarios":"Holding a reference to a plugin tool and invoking execute() after (a) the plugin providing the tool was uninstalled/disabled, (b) the plugin renamed or removed the tool, (c) reloadChangedLocalPlugins picked up edited plugin source that no longer exports that tool, or (d) a plugin failed to reload so its tools vanished from rawPluginTools.","commonSituations":"Hot-reload during development while editing plugin code; an agent/workflow captured the tool reference earlier and runs later after the plugin changed; the plugin file was deleted or moved; a syntax error in the plugin caused a reload that dropped its tools; tool name refactored without updating callers.","solutions":["Re-resolve the tool from the manager after plugin changes instead of caching the old reference.","Verify the plugin is still installed and enabled and that it still exports the tool under the same name.","Fix any plugin source errors that make reload drop the tool (check reload logs).","Restart the session/manager so the tool registry is rebuilt if hot-reload state is inconsistent."],"exampleFix":"// before\nconst tool = manager.getTool('search'); // cached\nawait tool.execute(args); // may throw after reload\n// after\nconst fresh = manager.getTool('search');\nif (!fresh) throw new Error('tool removed');\nawait fresh.execute(args);","handlingStrategy":"try-catch","validationCode":"const tool = manager.getTool?.(toolName) ?? manager.rawPluginTools?.[toolName];\nif (!tool || typeof tool.execute !== 'function') {\n  throw new Error(`Tool \"${toolName}\" unavailable; refusing to invoke stale proxy`);\n}","typeGuard":"function isLiveTool(t: unknown): t is { execute: (...args: any[]) => unknown } {\n  return typeof t === 'object' && t !== null && typeof (t as any).execute === 'function';\n}","tryCatchPattern":"try {\n  await pluginTool.execute(args);\n} catch (err) {\n  if (err instanceof Error && /is no longer available/.test(err.message)) {\n    // re-fetch the tool from the manager and retry once, or surface a 'plugin changed' error\n    const fresh = manager.getPluginTools()[toolName];\n    if (fresh) return fresh.execute(args);\n    throw new Error(`Tool ${toolName} was removed by a plugin reload`);\n  }\n  throw err;\n}","preventionTips":["Look up tools fresh from the manager on every use instead of caching proxies across reloads.","After editing plugin source, re-resolve tool references before running dependent agents/workflows.","Keep tool names stable across plugin refactors, or update all consumers in the same change."],"tags":["plugin","hot-reload","stale-reference","tools"],"backgroundTag":"stale-plugin-tool-reference","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}