can1357/oh-my-pi · error · ValueError
Plugin '${name}' is already registered
Error message
Plugin '${name}' is already registered What it means
PluginManager keeps a registry map of plugin names to constructors and forbids duplicate names so loadPlugin always resolves unambiguously. The ValueError fires on a second registerPlugin call with a name already in the registry — including the built-in names registered in the constructor.
Source
Thrown at packages/mnemopi/src/core/plugins.ts:275
override onInvalidate(_memoryId: string): void {}
}
export type PluginConstructor<T extends MnemopiPlugin = MnemopiPlugin> = new (config?: PluginConfig) => T;
export class PluginManager {
private readonly registry = new Map<string, PluginConstructor>();
private readonly instances = new Map<string, MnemopiPlugin>();
constructor(private readonly pluginDir = DEFAULT_PLUGIN_DIR) {
this.registerPlugin("logging", LoggingPlugin);
this.registerPlugin("metrics", MetricsPlugin);
this.registerPlugin("filter", FilterPlugin);
this.registerPlugin("compression", CompressionPlugin);
}
registerPlugin(name: string, pluginClass: PluginConstructor): void {
if (typeof pluginClass !== "function" || !(pluginClass.prototype instanceof MnemopiPlugin)) {
throw new TypeError("pluginClass must be a MnemopiPlugin subclass");
}
if (this.registry.has(name)) throw new ValueError(`Plugin '${name}' is already registered`);
this.registry.set(name, pluginClass);
}
loadPlugin(name: string, config: PluginConfig = {}): MnemopiPlugin {
const pluginClass = this.registry.get(name);
if (pluginClass === undefined) throw new ValueError(`Plugin '${name}' is not registered`);
if (this.instances.has(name)) throw new Error(`Plugin '${name}' is already loaded`);
const instance = new pluginClass(config);
instance.initialize();
this.instances.set(name, instance);
return instance;
}
unloadPlugin(name: string): void {
const instance = this.instances.get(name);
if (instance === undefined) throw new ValueError(`Plugin '${name}' is not loaded`);
this.instances.delete(name);
instance.shutdown();
}
listPlugins(): Array<Record<string, unknown>> {View on GitHub (pinned to 9690622007)
Solutions
- Pick a unique name for the new plugin before calling registerPlugin
- Call manager.hasPlugin(name) / listPlugins() first and only register when the name is absent
- If replacing a built-in, use a different name or the API's designated override path instead of re-registering
Example fix
// before
manager.registerPlugin("metrics", MyMetricsPlugin); // ValueError: built-in exists
// after
if (!manager.listPlugins().some(p => p.name === "metrics")) {
manager.registerPlugin("metrics", MyMetricsPlugin);
} Defensive patterns
Strategy: validation
Validate before calling
const taken = new Set(manager.listPlugins().map(p => p.name)); if (!taken.has(name)) manager.registerPlugin(name, pluginClass);
Try / catch
try {
manager.registerPlugin(name, pluginClass);
} catch (err) {
if (err instanceof ValueError && err.message.includes("already registered")) return; // idempotent re-init
throw err;
} Prevention
- Check listPlugins()/registry before registering
- Avoid re-running manager construction/initialization against a shared registry
- Prefix custom plugin names to avoid colliding with built-ins (metrics, filter, compression)
When it happens
Trigger: Calling registerPlugin("metrics", ...) or registerPlugin("filter", ...) or registerPlugin("compression", ...) after the manager constructor has already registered those built-ins; registering the same custom name twice.
Common situations: Re-initializing or hot-reloading a manager/module so the constructor runs twice; creating a second manager instance in the same process when the registry is shared; typo colliding with a built-in plugin name.
Related errors
- Relative plugin source paths must start with "./" — got: "${
- pluginClass must be a MnemopiPlugin subclass
- Plugin '${name}' is already loaded
- Unsupported language '{value}'. Supported: {}
- Unable to infer language from file extension: {}. Specify `l
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/67251f8810c50a43.
Report an issue: GitHub.