{"record":{"id":"4ca01aea4281e963","repo":"jdx/mise","slug":"local-plugin-source-is-not-a-directory","errorCode":null,"errorMessage":"local plugin source is not a directory: {}","messagePattern":"local plugin source is not a directory: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/plugins/mod.rs","lineNumber":559,"sourceCode":"    match source {\n        PluginSource::Git {\n            url,\n            git_ref: None,\n            subdir: None,\n        } if url == repository => path.is_absolute().then_some(path),\n        _ => None,\n    }\n}\n\npub(crate) fn validate_local_plugin_source(source: &Path, plugin_path: &Path) -> Result<()> {\n    if !source.exists() {\n        bail!(\n            \"local plugin directory does not exist: {}\",\n            display_path(source)\n        );\n    }\n    if !source.is_dir() {\n        bail!(\n            \"local plugin source is not a directory: {}\",\n            display_path(source)\n        );\n    }\n    let resolved_source = file::desymlink_path(source);\n    let resolved_plugin_path = match (plugin_path.parent(), plugin_path.file_name()) {\n        (Some(parent), Some(file_name)) => file::desymlink_path(parent).join(file_name),\n        _ => plugin_path.to_path_buf(),\n    };\n    if resolved_source\n        .ancestors()\n        .any(|path| file::paths_eq(path, &resolved_plugin_path))\n        || resolved_plugin_path\n            .ancestors()\n            .any(|path| file::paths_eq(path, &resolved_source))\n    {\n        bail!(\n            \"local plugin source cannot contain, be, or be inside the plugin install path: {}\",","sourceCodeStart":541,"sourceCodeEnd":577,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/plugins/mod.rs#L541-L577","documentation":"During local plugin install, mise requires the source to be a directory (a checked-out plugin repo). If the given path exists but is a regular file (or other non-directory), validate_local_plugin_source bails with this error.","triggerScenarios":"`mise plugin install <name> <path>` where <path> points at a file — e.g. a tarball/zip of the plugin, the plugin's main script, or a symlink resolving to a file — instead of the plugin's directory.","commonSituations":"Passing a downloaded plugin archive file instead of extracting it; pointing at the plugin's executable script rather than its repo root; a stale symlink whose target was replaced by a file.","solutions":["Pass the plugin directory, not a file — for archives, extract first (mkdir d && tar -xf plugin.tar.gz -C d) and install from d.","If pointing at a symlink, verify it resolves to a directory (ls -la <path>).","Re-clone the plugin repository and install from the cloned directory."],"exampleFix":"// before\nmise plugin install my-plugin ./my-plugin-v1.0.tar.gz\n// after\ntar -xf my-plugin-v1.0.tar.gz && mise plugin install my-plugin ./my-plugin","handlingStrategy":"validation","validationCode":"import std::path::Path;\nfn ensure_directory(p: &Path) -> std::io::Result<()> {\n    if !p.is_dir() {\n        return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!(\"{} is not a directory\", p.display())));\n    }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match install_local_plugin(name, path) {\n    Err(e) if e.to_string().contains(\"is not a directory\") => {\n        eprintln!(\"extract the archive and pass the extracted directory\");\n    }\n    r => r,\n}","preventionTips":["Always install from the plugin's repository root directory, never an archive file or single script.","Extract tarballs/zips before installing local plugins.","Verify with `test -d <path>` in shell scripts before invoking mise."],"tags":["filesystem","plugins","validation","path"],"backgroundTag":"path-is-not-a-directory","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}