ultraworkers/claw-code · error · std::io::Error
unable to derive an installable invocation name from '{}'
Error message
unable to derive an installable invocation name from '{}' What it means
`derive_skill_install_name` (commands/src/lib.rs:3912) tries two candidates in order: the skill's frontmatter name, then the fallback (directory name, or file stem for a bare .md file). Each is run through `sanitize_skill_invocation_name`, which lowercases, keeps only ASCII alphanumerics and `-_.`, converts whitespace and `/`/`\` to `-`, and trims leading/trailing separators. If BOTH candidates sanitize to an empty string, this InvalidInput error is raised.
Source
Thrown at rust/crates/commands/src/lib.rs:3912
std::io::ErrorKind::InvalidInput,
format!(
"skill source '{}' must be a directory with SKILL.md or a markdown file",
source.display()
),
))
}
fn derive_skill_install_name(
source: &SkillInstallSource,
declared_name: Option<&str>,
) -> std::io::Result<String> {
for candidate in [declared_name, source.fallback_name().as_deref()] {
if let Some(candidate) = candidate.and_then(sanitize_skill_invocation_name) {
return Ok(candidate);
}
}
Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!(
"unable to derive an installable invocation name from '{}'",
source.report_path().display()
),
))
}
fn sanitize_skill_invocation_name(candidate: &str) -> Option<String> {
let trimmed = candidate
.trim()
.trim_start_matches('/')
.trim_start_matches('$');
if trimmed.is_empty() {
return None;
}
let mut sanitized = String::new();View on GitHub (pinned to 08106b0c37)
Solutions
- Add or fix a `name:` field in the SKILL.md frontmatter using at least one ASCII letter or digit.
- Rename the source directory (or .md file) so its stem contains ASCII alphanumerics, e.g. `debug-skill`.
- Pre-check the sanitized result yourself: lowercase, drop non-[a-z0-9-_.] chars, and confirm something survives.
Example fix
# before: dir '调试技能/SKILL.md' with no frontmatter name /skills install ./调试技能 # unable to derive an installable invocation name from '...' # after: SKILL.md frontmatter --- name: debug-skill --- /skills install ./调试技能 # installs as 'debug-skill'
Defensive patterns
Strategy: validation
Validate before calling
fn sanitize_preview(name: &str) -> String {
name.trim().trim_start_matches(['/','$']).to_ascii_lowercase()
.chars().map(|c| if c.is_ascii_alphanumeric() || matches!(c,'-'|'_'|'.') { c } else if c.is_whitespace() || matches!(c,'/'|'\\') { '-' } else { '\0' })
.filter(|&c| c != '\0').collect() // approximate; empty result => will fail
}
// require a non-empty result containing an ascii alphanumeric before install Try / catch
if let Err(e) = install_skill(src, cwd) {
if e.to_string().contains("unable to derive an installable invocation name") {
// set frontmatter `name:` with ascii letters, or rename the dir/file
}
} Prevention
- Give every SKILL.md a frontmatter name with at least one ASCII letter/digit
- Avoid directory names that are only punctuation, emoji, or non-Latin script
- Non-ASCII letters are dropped (not transliterated) by the sanitizer
When it happens
Trigger: A skill directory named entirely in a non-Latin script (e.g. `调试技能` — every CJK char is dropped because only `is_ascii_alphanumeric` is kept); a name made solely of punctuation/emoji (`!!!`, `#1`); an empty frontmatter `name:` plus a punctuated directory name; a file stem like `---`.
Common situations: Skills authored by non-English teams whose directory name is the native-language skill name; markdown files named with decorative emoji prefixes; frontmatter missing a `name` field combined with a symbolic directory name.
Related errors
- skill source '{}' must be a directory with SKILL.md or a mar
- unable to resolve a skills install root; set CLAW_CONFIG_HOM
- skill source '{}' not found: {e}
- skill directory '{}' must contain SKILL.md
- old_string and new_string must differ
AI-assisted analysis of ultraworkers/claw-code@08106b0c37 (2026-08-18).
Data as JSON: /api/errors/975b7f4dce496d86.
Report an issue: GitHub.