{"record":{"id":"3136fc2035349d8b","repo":"Hmbown/CodeWhale","slug":"skill-name-must-be-a-single-path-safe-segment-got","errorCode":null,"errorMessage":"skill name must be a single path-safe segment (got '{name}')","messagePattern":"skill name must be a single path-safe segment \\(got '(.+?)'\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/skills/install.rs","lineNumber":1546,"sourceCode":"    }\n    for component in path.components() {\n        match component {\n            Component::ParentDir => return false,\n            Component::Prefix(_) | Component::RootDir => return false,\n            _ => {}\n        }\n    }\n    true\n}\n\nfn skill_target_path(name: &str, skills_dir: &Path) -> Result<PathBuf> {\n    let name = validate_skill_name_segment(name)?;\n    Ok(skills_dir.join(name))\n}\n\npub(crate) fn validate_skill_name_segment(name: &str) -> Result<&str> {\n    if name.is_empty() || name.trim() != name || name.chars().any(char::is_whitespace) {\n        bail!(\"skill name must be a single path-safe segment (got '{name}')\");\n    }\n    if name == \".\" || name == \"..\" || name.contains('/') || name.contains('\\\\') {\n        bail!(\"skill name must be a single path-safe segment (got '{name}')\");\n    }\n    let mut components = Path::new(name).components();\n    if !matches!(components.next(), Some(Component::Normal(_))) || components.next().is_some() {\n        bail!(\"skill name must be a single path-safe segment (got '{name}')\");\n    }\n    Ok(name)\n}\n\nfn ensure_target_within_skills_dir(target: &Path, skills_dir: &Path) -> Result<()> {\n    let skills_dir = fs::canonicalize(skills_dir)\n        .with_context(|| format!(\"failed to resolve {}\", skills_dir.display()))?;\n    let target = fs::canonicalize(target)\n        .with_context(|| format!(\"failed to resolve {}\", target.display()))?;\n    if !target.starts_with(&skills_dir) {\n        bail!(","sourceCodeStart":1528,"sourceCodeEnd":1564,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/skills/install.rs#L1528-L1564","documentation":"First clause of validate_skill_name_segment: a skill name must be non-empty, equal to its own trim (no leading or trailing whitespace), and contain no whitespace characters at all. The check runs before any path join, so install/uninstall/update targeting refuses whitespace-bearing names outright.","triggerScenarios":"Passing a skill name like ' my-skill', 'my skill', or '' to install/uninstall/update; typically from shell word-splitting or quoted arguments containing stray spaces.","commonSituations":"Copy-paste with a trailing newline or space, names derived from display labels containing spaces, and scripts passing unquoted empty variables.","solutions":["Strip whitespace and quote the argument when invoking skill commands.","Use hyphens instead of spaces in skill names (the ecosystem convention).","Validate names in wrappers with the same rule before calling the API."],"exampleFix":"# before\n/skill uninstall \"my skill\"\n\n# after\n/skill uninstall my-skill","handlingStrategy":"validation","validationCode":"fn name_has_no_whitespace(name: &str) -> bool {\n    !name.is_empty() && name.trim() == name && !name.chars().any(char::is_whitespace)\n}","typeGuard":"fn is_valid_skill_name(name: &str) -> bool {\n    !name.is_empty()\n        && name.trim() == name\n        && !name.chars().any(char::is_whitespace)\n        && name != \".\" && name != \"..\"\n        && !name.contains('/') && !name.contains('\\\\')\n        && {\n            let mut c = std::path::Path::new(name).components();\n            matches!(c.next(), Some(std::path::Component::Normal(_))) && c.next().is_none()\n        }\n}","tryCatchPattern":null,"preventionTips":["Derive skill names with a slugify step (spaces to hyphens) at the input boundary.","Quote skill names in shell invocations to avoid word-splitting artifacts.","Trim pasted input before treating it as a name."],"tags":["skills","name-validation","whitespace","security","rust"],"backgroundTag":"path-traversal-rejected","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}