zellij-org/zellij · error · anyhow::Error

failed to load plugin from disk

Error message

failed to load plugin from disk

What it means

Initial value of last_err in PluginTag/RunPluginLocation::resolve_wasm_bytes (zellij-utils/input/plugins.rs). Zellij tries a chain of candidate paths for every configured plugin — the raw path, path + '.wasm', <plugin_dir>/<path>.wasm, and <system_data_dir>/plugins/<path>.wasm — plus the built-in ASSET_MAP for 'zellij:'-prefixed plugins. If no candidate yields bytes, this seed error is returned with every attempted path attached as anyhow context, so the final message enumerates the whole failed lookup chain.

Source

Thrown at zellij-utils/src/input/plugins.rs:129

            plugin_dir.join(&self.path).with_extension("wasm"),
        ];
        #[cfg(not(target_family = "wasm"))]
        paths.push(
            crate::home::system_data_dir()
                .join("plugins")
                .join(&self.path)
                .with_extension("wasm"),
        );
        // Throw out dupes, because it's confusing to read that zellij checked the same plugin
        // location multiple times. Do NOT sort the vector here, because it will break the lookup!
        paths.dedup();

        // This looks weird and usually we would handle errors like this differently, but in this
        // case it's helpful for users and developers alike. This way we preserve all the lookup
        // errors and can report all of them back. We must initialize `last_err` with something,
        // and since the user will only get to see it when loading a plugin failed, we may as well
        // spell it out right here.
        let mut last_err: Result<Vec<u8>> = Err(anyhow!("failed to load plugin from disk"));
        for path in paths {
            // Check if the plugin path matches an entry in the asset map. If so, load it directly
            // from memory, don't bother with the disk.
            #[cfg(not(target_family = "wasm"))]
            if !cfg!(feature = "disable_automatic_asset_installation") && self.is_builtin() {
                let asset_path = PathBuf::from("plugins").join(&path);
                if let Some(bytes) = ASSET_MAP.get(&asset_path) {
                    log::debug!("Loaded plugin '{}' from internal assets", path.display());

                    if plugin_dir.join(&path).with_extension("wasm").exists() {
                        log::info!(
                            "Plugin '{}' exists in the 'PLUGIN DIR' at '{}' but is being ignored",
                            path.display(),
                            plugin_dir.display()
                        );
                    }

                    return Ok(bytes.to_vec());

View on GitHub (pinned to 98a0837077)

Solutions

  1. Verify the plugin name/path spelling in the layout/config against the actual file name
  2. Install the wasm into the plugin dir (usually ~/.local/share/zellij/plugins/<name>.wasm) or reference it by absolute path
  3. For builtins use the 'zellij:' prefix (e.g. zellij:tab-bar) so the asset map resolves it
  4. Run `zellij setup --check` to confirm the resolved data/plugin directories, and check permissions on the file
  5. Reinstall zellij if built with disable_automatic_asset_installation and builtin plugins are missing from disk

Example fix

// before (layout.kdl)
layout {
    pane plugin="my-status" // not found anywhere in the lookup chain
}

// after
layout {
    pane plugin="file:/home/bob/.local/share/zellij/plugins/my-status.wasm"
}
Defensive patterns

Strategy: validation

Validate before calling

// before starting a session with a layout referencing a plugin
let candidates = [
    plugin_dir.join(&name).with_extension("wasm"),
    std::path::PathBuf::from(&name),
];
let builtin = name.starts_with("zellij:");
if !builtin && !candidates.iter().any(|p| p.is_file()) {
    anyhow::bail!("plugin '{name}' not found in any lookup path; install it or use an absolute path");
}

Type guard

fn plugin_resolves(name: &str, plugin_dir: &std::path::Path) -> bool {
    name.starts_with("zellij:")
        || std::path::Path::new(name).is_file()
        || plugin_dir.join(name).with_extension("wasm").is_file()
        || crate::home::system_data_dir().join("plugins").join(name).with_extension("wasm").is_file()
}

Try / catch

match run_plugin_location.resolve_wasm_bytes(&plugin_dir) {
    Ok(bytes) => spawn_plugin(bytes)?,
    Err(e) if e.to_string().contains("failed to load plugin from disk") => {
        // error chain lists every attempted path — surface it, skip this plugin, keep session alive
        log::error!("skipping plugin, all lookups failed: {e:#}");
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A config/layout names a plugin whose wasm exists in none of the lookup locations: typo'd or nonexistent plugin name, plugin file not installed into the plugin directory, unreadable file (permissions), or a zellij: builtin referenced in a build compiled with disable_automatic_asset_installation but without the asset installed to disk.

Common situations: Fresh machine where third-party plugins were never downloaded; custom plugin path relative vs absolute confusion (relative resolves against plugin dir, not cwd); wrong XDG_DATA_HOME/DATA_DIR so the plugin dir differs from expectation; permission-denied on the .wasm; typo like 'tabbar' instead of 'tab-bar'.

Related errors


AI-assisted analysis of zellij-org/zellij@98a0837077 (2026-08-16). Data as JSON: /api/errors/5d42c4b9cad1b7fa. Report an issue: GitHub.