sinelaw/fresh · error

Plugin ' ' not found

Error message

Plugin '{}' not found

What it means

Raised by `unload_plugin_internal` when the given plugin name is not a key in the loaded-plugins `HashMap`. The runtime only unloads plugins it has previously loaded (removing their commands, i18n strings, and runtime state); requesting an unload for an unknown name returns this error instead of silently succeeding.

Solutions

  1. Check that the plugin is currently loaded (name exactly matches its file stem) before calling unload.
  2. Treat 'not found' on unload as idempotent success if your flow may unload twice.
  3. Verify the name used at load time matches; fix any normalization (case, hyphen/underscore) differences.
  4. If the plugin failed to load earlier, clean up its state via the load error path instead of calling unload.

Example fix

// before
unload_plugin_internal(runtime, &mut plugins, "My-Plugin").await?;

// after
if plugins.contains_key("my-plugin") {
    unload_plugin_internal(runtime, &mut plugins, "my-plugin").await?;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if !plugins.contains_key(name) {
    eprintln!("plugin '{}' is not loaded; skipping unload", name);
    return Ok(());
}

Try / catch

if let Err(e) = unload_plugin_internal(Rc::clone(&runtime), &mut plugins, name).await {
    if e.to_string().contains("not found") {
        tracing::debug!("unload ignored: plugin '{}' not loaded", name); // treat as idempotent
    } else {
        return Err(e);
    }
}

Prevention

When it happens

Trigger: Calling the unload API (directly or via a plugin-management command) with a `name` that was never loaded, already unloaded, or spelled differently than the registered name (the filename stem is the key).

Common situations: Double-unload after an earlier unload or failed load; unloading after a reload that renamed the plugin; name mismatch between the requested name and the file stem (e.g. `my_plugin` vs `my-plugin`); unloading a plugin whose initial load failed so it never entered the map.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13). Data as JSON: /api/errors/43543f969c47705d. Report an issue: GitHub.

Appendix: source

Thrown at crates/fresh-plugin-runtime/src/thread.rs:1952

        // Unregister i18n strings
        runtime
            .borrow_mut()
            .services
            .unregister_plugin_strings(name);

        // Remove all commands registered by this plugin
        runtime
            .borrow()
            .services
            .unregister_commands_by_plugin(name);

        // Clean up plugin runtime state (context, event handlers, actions, callbacks)
        runtime.borrow().cleanup_plugin(name);

        Ok(())
    } else {
        Err(anyhow!("Plugin '{}' not found", name))
    }
}

/// Reload a plugin
async fn reload_plugin_internal(
    runtime: Rc<RefCell<QuickJsBackend>>,
    plugins: &mut HashMap<String, TsPluginInfo>,
    name: &str,
) -> Result<()> {
    let path = plugins
        .get(name)
        .ok_or_else(|| anyhow!("Plugin '{}' not found", name))?
        .path
        .clone();

    unload_plugin_internal(Rc::clone(&runtime), plugins, name)?;
    load_plugin_internal(runtime, plugins, &path).await?;

View on GitHub (pinned to 67894ca546)