{"record":{"id":"0f9497762cbae821","repo":"can1357/oh-my-pi","slug":"plugin-name-is-already-loaded","errorCode":null,"errorMessage":"Plugin '${name}' is already loaded","messagePattern":"Plugin '(.+?)' is already loaded","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/mnemopi/src/core/plugins.ts","lineNumber":281,"sourceCode":"\tprivate readonly registry = new Map<string, PluginConstructor>();\n\tprivate readonly instances = new Map<string, MnemopiPlugin>();\n\tconstructor(private readonly pluginDir = DEFAULT_PLUGIN_DIR) {\n\t\tthis.registerPlugin(\"logging\", LoggingPlugin);\n\t\tthis.registerPlugin(\"metrics\", MetricsPlugin);\n\t\tthis.registerPlugin(\"filter\", FilterPlugin);\n\t\tthis.registerPlugin(\"compression\", CompressionPlugin);\n\t}\n\tregisterPlugin(name: string, pluginClass: PluginConstructor): void {\n\t\tif (typeof pluginClass !== \"function\" || !(pluginClass.prototype instanceof MnemopiPlugin)) {\n\t\t\tthrow new TypeError(\"pluginClass must be a MnemopiPlugin subclass\");\n\t\t}\n\t\tif (this.registry.has(name)) throw new ValueError(`Plugin '${name}' is already registered`);\n\t\tthis.registry.set(name, pluginClass);\n\t}\n\tloadPlugin(name: string, config: PluginConfig = {}): MnemopiPlugin {\n\t\tconst pluginClass = this.registry.get(name);\n\t\tif (pluginClass === undefined) throw new ValueError(`Plugin '${name}' is not registered`);\n\t\tif (this.instances.has(name)) throw new Error(`Plugin '${name}' is already loaded`);\n\t\tconst instance = new pluginClass(config);\n\t\tinstance.initialize();\n\t\tthis.instances.set(name, instance);\n\t\treturn instance;\n\t}\n\tunloadPlugin(name: string): void {\n\t\tconst instance = this.instances.get(name);\n\t\tif (instance === undefined) throw new ValueError(`Plugin '${name}' is not loaded`);\n\t\tthis.instances.delete(name);\n\t\tinstance.shutdown();\n\t}\n\tlistPlugins(): Array<Record<string, unknown>> {\n\t\tconst result: Array<Record<string, unknown>> = [];\n\t\tfor (const [name, pluginClass] of this.registry)\n\t\t\tresult.push({\n\t\t\t\tname,\n\t\t\t\tclass: pluginClass.name,\n\t\t\t\tloaded: this.instances.has(name),","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/mnemopi/src/core/plugins.ts#L263-L299","documentation":"loadPlugin enforces one live instance per plugin name; the instances map is the source of truth. Calling loadPlugin a second time for an already-loaded plugin throws instead of returning the existing instance or resetting it.","triggerScenarios":"Calling manager.loadPlugin(\"metrics\") twice without an intervening unloadPlugin(\"metrics\"); application startup code and a getPlugin/plugin accessor both invoking loadPlugin for the same name.","commonSituations":"Double initialization paths (bootstrap plus lazy accessor); retrying startup after a partial failure that already loaded the plugin; re-running an init script against a long-lived manager.","solutions":["Call unloadPlugin(name) before loadPlugin if a fresh instance is intended","Use getPlugin(name) to fetch the already-loaded instance instead of loadPlugin","Guard loads: only call loadPlugin when the instance is not yet present"],"exampleFix":"// before\nconst a = manager.loadPlugin(\"metrics\");\nconst b = manager.loadPlugin(\"metrics\"); // Error: already loaded\n// after\nconst b = manager.getPlugin(\"metrics\") ?? manager.loadPlugin(\"metrics\");","handlingStrategy":"try-catch","validationCode":"// fetch-or-load instead of blind load\nconst plugin = manager.getPlugin(\"metrics\") ?? manager.loadPlugin(\"metrics\");","typeGuard":null,"tryCatchPattern":"function loadOnce(name: string, config?: PluginConfig) {\n  try {\n    return manager.loadPlugin(name, config);\n  } catch (err) {\n    if (err instanceof Error && err.message.includes(\"already loaded\")) {\n      return manager.getPlugin(name);\n    }\n    throw err;\n  }\n}","preventionTips":["Centralize plugin loading in one accessor (get-or-load)","Never call loadPlugin twice for the same name; use unloadPlugin first when a fresh instance is needed","Track loaded state in your bootstrap to avoid double init paths"],"tags":["plugin","duplicate-key","lifecycle"],"backgroundTag":"plugin-already-loaded","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}