{"record":{"id":"e311e5d03157e52a","repo":"can1357/oh-my-pi","slug":"plugin-name-is-not-loaded","errorCode":null,"errorMessage":"Plugin '${name}' is not loaded","messagePattern":"Plugin '(.+?)' is not loaded","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/mnemopi/src/core/plugins.ts","lineNumber":289,"sourceCode":"\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),\n\t\t\t\tinstance: this.instances.get(name) ?? null,\n\t\t\t});\n\t\treturn result;\n\t}\n\tgetPlugin(name: string): MnemopiPlugin | null {\n\t\tconst loaded = this.instances.get(name);\n\t\tif (loaded !== undefined) return loaded;\n\t\tif (this.registry.has(name)) return this.loadPlugin(name);","sourceCodeStart":271,"sourceCodeEnd":307,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/mnemopi/src/core/plugins.ts#L271-L307","documentation":"unloadPlugin requires the named plugin to have a live instance in the instances map; otherwise it raises a ValueError. You can only unload plugins that were successfully loaded with loadPlugin.","triggerScenarios":"Calling manager.unloadPlugin(\"metrics\") when \"metrics\" was registered but never loaded; unloading after a previous unloadPlugin already removed it; unloading a name that only exists in the registry.","commonSituations":"Teardown/cleanup code assuming all registered plugins were loaded; double-stop paths (shutdown handler plus explicit unload); loading failures earlier meant the instance never existed.","solutions":["Check that the plugin was loaded before unloading (track your own loaded set or use the instances/registry accessor)","Make cleanup idempotent: wrap unloadPlugin in a check or catch the ValueError and ignore it","Fix the load path so the plugin is actually loaded before teardown runs"],"exampleFix":"// before\nmanager.unloadPlugin(\"metrics\"); // throws if never loaded\n// after\ntry {\n  manager.unloadPlugin(\"metrics\");\n} catch (err) {\n  if (!(err instanceof ValueError)) throw err; // already unloaded — fine\n}","handlingStrategy":"try-catch","validationCode":"// only unload names you successfully loaded\nconst loadedNames = new Set<string>();\nloadedNames.add(manager.loadPlugin(\"metrics\") && \"metrics\");\nif (loadedNames.has(\"metrics\")) manager.unloadPlugin(\"metrics\");","typeGuard":null,"tryCatchPattern":"function unloadSafe(name: string) {\n  try {\n    manager.unloadPlugin(name);\n  } catch (err) {\n    if (err instanceof ValueError && err.message.includes(\"not loaded\")) return; // idempotent teardown\n    throw err;\n  }\n}","preventionTips":["Make shutdown/teardown idempotent by catching the not-loaded case","Only unload plugins your code explicitly loaded","Never assume every registered plugin has an instance"],"tags":["plugin","unregistered","lifecycle"],"backgroundTag":"plugin-not-loaded","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}