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
- Verify the plugin name/path spelling in the layout/config against the actual file name
- Install the wasm into the plugin dir (usually ~/.local/share/zellij/plugins/<name>.wasm) or reference it by absolute path
- For builtins use the 'zellij:' prefix (e.g. zellij:tab-bar) so the asset map resolves it
- Run `zellij setup --check` to confirm the resolved data/plugin directories, and check permissions on the file
- 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
- Use absolute file: paths or the zellij: builtin prefix to make resolution deterministic
- Install third-party wams into the plugin dir reported by `zellij setup --check`
- Validate layouts in CI by launching a throwaway session before shipping config
- Remember the chain: name, name.wasm, <plugin_dir>/name.wasm, <data_dir>/plugins/name.wasm
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
- Plugin is not stored in memory
- Failed to serialize user configuration: {:?}
- Failed to parse layout: {:?}
- Failed to deserialize layout: {}
- Failed to parse plugin location: {}
AI-assisted analysis of zellij-org/zellij@98a0837077 (2026-08-16).
Data as JSON: /api/errors/5d42c4b9cad1b7fa.
Report an issue: GitHub.