tonhowtf/omniget · error

external_data_cache: plugin_id must not be empty

Error message

external_data_cache: plugin_id must not be empty

What it means

`external_data_cache` in plugin_host validates its `plugin_id` parameter before using it to build a cache directory path; an empty id would yield a malformed/ambiguous path. It bails via anyhow before any filesystem work.

Solutions

  1. Ensure the plugin manifest declares a non-empty `id` before loading
  2. Validate plugin_id at the loader boundary before invoking cache APIs
  3. Default to a stable fallback id or reject the plugin at registration time

Example fix

// before
let cache = host.external_data_cache(&manifest.id, ns)?; // id may be ""
// after
ensure!(!manifest.id.is_empty(), "plugin manifest missing id");
let cache = host.external_data_cache(&manifest.id, ns)?;
Defensive patterns

Strategy: validation

Validate before calling

if plugin_id.is_empty() { return Err(anyhow!("plugin id required before cache access")); }

Type guard

fn valid_plugin_id(id: &str) -> bool { !id.is_empty() && !id.contains(['/', '\\', ':', '\0']) }

Try / catch

let cache = host.external_data_cache(id, ns)
    .with_context(|| format!("cache path for plugin {id:?}"))?;

Prevention

When it happens

Trigger: Calling plugin-host cache APIs with `plugin_id: ""` — typically a plugin manifest missing its id field, or code constructing the cache key from an absent/undefined field.

Common situations: Hand-edited or corrupted plugin manifest without `id`; migration/upgrade left the id field empty; caller passes a variable that defaulted to String::new().

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/79237a99c5bc4cb7. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/src/plugin_host.rs:115

        let bin_name = _tool.to_string();

        let managed_path = managed_dir.join(&bin_name);
        if managed_path.exists() {
            return Some(managed_path);
        }

        which::which(&bin_name).ok()
    }

    fn default_output_dir(&self) -> PathBuf {
        dirs::download_dir()
            .or_else(dirs::home_dir)
            .unwrap_or_else(|| PathBuf::from("."))
    }

    fn external_data_cache(&self, plugin_id: &str, namespace: &str) -> anyhow::Result<PathBuf> {
        if plugin_id.is_empty() {
            anyhow::bail!("external_data_cache: plugin_id must not be empty");
        }
        if namespace.is_empty() {
            anyhow::bail!("external_data_cache: namespace must not be empty");
        }
        if plugin_id.contains(['/', '\\', ':', '\0']) || namespace.contains(['/', '\\', ':', '\0'])
        {
            anyhow::bail!(
                "external_data_cache: plugin_id/namespace must not contain path separators or null bytes"
            );
        }

        // portable installs keep every file next to the app, so the cache
        // lives under the app data dir instead of the OS cache dir
        let base = if std::env::var("OMNIGET_PORTABLE").is_ok() {
            omniget_core::core::paths::app_data_dir()
                .ok_or_else(|| anyhow::anyhow!("external_data_cache: app data dir unavailable"))?
                .join("cache")
        } else {

View on GitHub (pinned to 8600b91f42)