can1357/oh-my-pi · error · ValueError

Plugin '${name}' is not loaded

Error message

Plugin '${name}' is not loaded

What it means

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.

Source

Thrown at packages/mnemopi/src/core/plugins.ts:289

	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>> {
		const result: Array<Record<string, unknown>> = [];
		for (const [name, pluginClass] of this.registry)
			result.push({
				name,
				class: pluginClass.name,
				loaded: this.instances.has(name),
				instance: this.instances.get(name) ?? null,
			});
		return result;
	}
	getPlugin(name: string): MnemopiPlugin | null {
		const loaded = this.instances.get(name);
		if (loaded !== undefined) return loaded;
		if (this.registry.has(name)) return this.loadPlugin(name);

View on GitHub (pinned to 9690622007)

Solutions

  1. Check that the plugin was loaded before unloading (track your own loaded set or use the instances/registry accessor)
  2. Make cleanup idempotent: wrap unloadPlugin in a check or catch the ValueError and ignore it
  3. Fix the load path so the plugin is actually loaded before teardown runs

Example fix

// before
manager.unloadPlugin("metrics"); // throws if never loaded
// after
try {
  manager.unloadPlugin("metrics");
} catch (err) {
  if (!(err instanceof ValueError)) throw err; // already unloaded — fine
}
Defensive patterns

Strategy: try-catch

Validate before calling

// only unload names you successfully loaded
const loadedNames = new Set<string>();
loadedNames.add(manager.loadPlugin("metrics") && "metrics");
if (loadedNames.has("metrics")) manager.unloadPlugin("metrics");

Try / catch

function unloadSafe(name: string) {
  try {
    manager.unloadPlugin(name);
  } catch (err) {
    if (err instanceof ValueError && err.message.includes("not loaded")) return; // idempotent teardown
    throw err;
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/e311e5d03157e52a. Report an issue: GitHub.