zellij-org/zellij · error
failed to load plugin from disk
Error message
failed to load plugin from disk
What it means
Seed error of PluginConfig::resolve_wasm_bytes (zellij-utils/src/input/plugins.rs): the lookup tries, in order, the raw path, path + '.wasm', plugin_dir/path.wasm, and the system data dir's plugins/path.wasm (plus the builtin ASSET_MAP for 'zellij:' plugins unless built with disable_automatic_asset_installation). This message is the initial value of last_err; the surfaced error chains each failed path's io::Error as context, so it means NO candidate location yielded readable wasm bytes.
Solutions
- Check the chained per-path io errors in the full error message — they list exactly which locations were tried; make your wasm exist at one of them (usually the plugin dir, e.g. ~/.local/share/zellij/plugins/).
- Fix the plugin name/path in your layout or config: builtins must match exactly (e.g. "zellij:tab-bar"), custom plugins should be the file stem or path relative to the plugin dir.
- On builds without automatic asset installation, run `zellij setup --dump-plugins` to materialize builtin wasm into the plugin dir.
- Verify file permissions on the wasm and that it is a real wasm module, not an HTML error page or empty file.
Example fix
// before: name matches no builtin and no file
layout {
pane {
plugin "tabbar" --flag
}
}
// after: exact builtin name (loaded from ASSET_MAP)
layout {
pane {
plugin "zellij:tab-bar" --flag
}
} Defensive patterns
Strategy: validation
Validate before calling
// pre-check every location resolve_wasm_bytes will try
use zellij_utils::home::system_data_dir;
fn plugin_resolvable(path: &Path, plugin_dir: &Path) -> bool {
let candidates = [
path.to_path_buf(),
path.with_extension("wasm"),
plugin_dir.join(path).with_extension("wasm"),
system_data_dir().join("plugins").join(path).with_extension("wasm"),
];
candidates.iter().any(|p| p.is_file())
} Try / catch
match plugin.resolve_wasm_bytes(&plugin_dir) {
Ok(bytes) => bytes,
Err(e) => {
// the context chain lists each attempted path — surface it to the user
anyhow::bail!("plugin '{}' could not be loaded. Checked: {e:#}", plugin.path.display());
},
} Prevention
- Use exact builtin names ("zellij:tab-bar", "zellij:status-bar") in layouts/configs.
- Place custom wasm under the plugin dir (default ~/.local/share/zellij/plugins/) and reference it by file stem or relative path.
- On disable_automatic_asset_installation builds, run `zellij setup --dump-plugins` during packaging.
- Smoke-test configs in CI with `zellij setup --check` so missing plugins fail before runtime.
When it happens
Trigger: A `run "name"` / plugin entry whose wasm exists nowhere: typo'd builtin name (so the ASSET_MAP lookup misses and disk fallbacks fail), custom plugin path not relative to the plugin dir nor absolute-and-existing, unreadable file (permissions), or a distro build with disable_automatic_asset_installation where builtin wasm was never dumped to disk (then you get BuiltinPluginMissing wrapping this).
Common situations: Config/layout referencing 'tab-bar' as 'tabbar'; custom plugin placed in the wrong directory; plugins not installed via `zellij setup --dump-plugins` on feature-restricted builds; upgrading zellij renamed/moved a builtin plugin.
Related errors
- cannot acquire poisoned lock
- cannot acquire poisoned lock for
- Character key needs character data
- Failed to find active pane
- failed to find active pane id for client
AI-assisted analysis of zellij-org/zellij@5cb5df5cce (2026-08-19).
Data as JSON: /api/errors/5d42c4b9cad1b7fa.
Report an issue: GitHub.
Appendix: 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 5cb5df5cce)