{"record":{"id":"161c07e37e0485bf","repo":"Hmbown/CodeWhale","slug":"local-install-path-must-not-be-empty","errorCode":null,"errorMessage":"local install path must not be empty","messagePattern":"local install path must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/plugins/install/mod.rs","lineNumber":136,"sourceCode":"        {\n            let source = InstallSource::parse(trimmed)?;\n            return match source {\n                InstallSource::GitHubRepo(_) | InstallSource::DirectUrl(_) => {\n                    remote_bundle_path(&source)?;\n                    Ok(Self::Remote(source))\n                }\n                InstallSource::Registry(_) => {\n                    unreachable!(\"prefixed specs never parse as a registry name\")\n                }\n            };\n        }\n        Self::local(trimmed)\n    }\n\n    fn local(spec: &str) -> Result<Self> {\n        let trimmed = spec.trim();\n        if trimmed.is_empty() {\n            bail!(\"local install path must not be empty\");\n        }\n        Ok(Self::LocalPath(PathBuf::from(trimmed)))\n    }\n}\n\n/// Select one manifest-rooted bundle from a repository archive. The fragment\n/// is local extraction metadata, never a path sent to or executed by a server.\nfn remote_bundle_path(source: &InstallSource) -> Result<Option<String>> {\n    let InstallSource::DirectUrl(raw) = source else {\n        return Ok(None);\n    };\n    let url = reqwest::Url::parse(raw).context(\"invalid plugin archive URL\")?;\n    let Some(fragment) = url.fragment() else {\n        return Ok(None);\n    };\n    let path = fragment\n        .strip_prefix(\"path=\")\n        .context(\"plugin archive fragment must be #path=<bundle-directory>\")?;","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/plugins/install/mod.rs#L118-L154","documentation":"PluginInstallSource::local handles the `path:<dir>` (and fallback) branch of source parsing. It trims the spec and rejects an empty remaining value, because a local plugin install must point at a concrete directory containing the plugin bundle.","triggerScenarios":"Passing `path:` with nothing after the prefix (e.g. `path:`, `path:   `), or falling through to local() with an empty/whitespace string from a config field or unquoted shell variable.","commonSituations":"Config entry `source = \"path:\"` with the directory accidentally omitted; `path:$PLUGIN_DIR` where PLUGIN_DIR is empty; copy-paste dropping the actual path.","solutions":["Supply the directory after the prefix, e.g. `path:/home/me/plugins/myplugin`","Fix the unset shell/config variable that emptied the path","Validate the path is non-empty and exists before calling parse"],"exampleFix":"// before\nsource = \"path:\"\n// after\nsource = \"path:./plugins/my-plugin\"","handlingStrategy":"validation","validationCode":"fn local_path_spec_ok(spec: &str) -> bool { spec.strip_prefix(\"path:\").map_or(true, |p| !p.trim().is_empty()) }","typeGuard":null,"tryCatchPattern":"match PluginInstallSource::parse(spec) { Err(e) if e.to_string().contains(\"local install path must not be empty\") => eprintln!(\"path: prefix requires a directory\"), other => other?, }","preventionTips":["Check path exists and is a directory before install","Avoid unquoted variable expansion that can empty the path","Validate `path:` specs at config-load time"],"tags":["validation","empty-input","plugins","path"],"backgroundTag":"empty-required-field","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}