Hmbown/CodeWhale · error
skill name must be a single path-safe segment (got '{name}')
Error message
skill name must be a single path-safe segment (got '{name}') What it means
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.
Source
Thrown at crates/tui/src/skills/install.rs:1546
}
for component in path.components() {
match component {
Component::ParentDir => return false,
Component::Prefix(_) | Component::RootDir => return false,
_ => {}
}
}
true
}
fn skill_target_path(name: &str, skills_dir: &Path) -> Result<PathBuf> {
let name = validate_skill_name_segment(name)?;
Ok(skills_dir.join(name))
}
pub(crate) fn validate_skill_name_segment(name: &str) -> Result<&str> {
if name.is_empty() || name.trim() != name || name.chars().any(char::is_whitespace) {
bail!("skill name must be a single path-safe segment (got '{name}')");
}
if name == "." || name == ".." || name.contains('/') || name.contains('\\') {
bail!("skill name must be a single path-safe segment (got '{name}')");
}
let mut components = Path::new(name).components();
if !matches!(components.next(), Some(Component::Normal(_))) || components.next().is_some() {
bail!("skill name must be a single path-safe segment (got '{name}')");
}
Ok(name)
}
fn ensure_target_within_skills_dir(target: &Path, skills_dir: &Path) -> Result<()> {
let skills_dir = fs::canonicalize(skills_dir)
.with_context(|| format!("failed to resolve {}", skills_dir.display()))?;
let target = fs::canonicalize(target)
.with_context(|| format!("failed to resolve {}", target.display()))?;
if !target.starts_with(&skills_dir) {
bail!(View on GitHub (pinned to 0c42157ee5)
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.
Example fix
# before /skill uninstall "my skill" # after /skill uninstall my-skill
Defensive patterns
Strategy: validation
Validate before calling
fn name_has_no_whitespace(name: &str) -> bool {
!name.is_empty() && name.trim() == name && !name.chars().any(char::is_whitespace)
} Type guard
fn is_valid_skill_name(name: &str) -> bool {
!name.is_empty()
&& name.trim() == name
&& !name.chars().any(char::is_whitespace)
&& name != "." && name != ".."
&& !name.contains('/') && !name.contains('\\')
&& {
let mut c = std::path::Path::new(name).components();
matches!(c.next(), Some(std::path::Component::Normal(_))) && c.next().is_none()
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Copy-paste with a trailing newline or space, names derived from display labels containing spaces, and scripts passing unquoted empty variables.
Related errors
- download {url} exceeds compressed size cap of {compressed_ca
- external credential path changed for {}; consent covers {},
- persistent allow rules must be scoped to a workspace
- persistent command allow rules must use exact matching
- persistent allow rules must match an exact command or path
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/3136fc2035349d8b.
Report an issue: GitHub.