zeroclaw-labs/zeroclaw · error · anyhow::Error
Invalid skill name: {name}
Error message
Invalid skill name: {name} What it means
`skills remove <name>` joins the name onto bundle/global skill directories to locate and delete them, so any name containing '..', '/', or '\' is rejected up front as a path-traversal payload. Only a bare skill directory name is accepted; location scoping belongs to the --bundle and --agent flags, never to the name argument.
Source
Thrown at src/skills/mod.rs:381
"cli-skills-install-into-bundle",
&[("alias", alias)],
)
),
SkillLocation::Global { .. } => println!(
"{}",
get_required_cli_string("cli-skills-install-global-note")
),
}
Ok(())
}
crate::SkillCommands::Remove {
name,
agent,
bundle,
} => {
// Reject path traversal attempts
if name.contains("..") || name.contains('/') || name.contains('\\') {
anyhow::bail!("Invalid skill name: {name}");
}
let status = console::style("✓").green().bold().to_string();
if let Some(ref a) = agent
&& config.agent(a).is_none()
{
anyhow::bail!(
"{}",
get_required_cli_string_with_args(
"cli-skills-agent-not-configured",
&[("alias", a)],
)
);
}
// Explicit bundle: archive through the service (recoverable).
if let Some(ref b) = bundle {
let service = SkillsService::new(config, config.install_root_dir());View on GitHub (pinned to 88bb9c8533)
Solutions
- Pass only the skill's directory name: `skills remove foo`
- Scope the search with --bundle <alias> or --agent <alias> instead of embedding a path in the name
- Quote the argument so the shell neither expands nor splits it
- If you genuinely need to remove a nested path, delete it manually after inspecting it
Example fix
# before zeroclaw skills remove shared/skills/foo # after zeroclaw skills remove foo --bundle shared
Defensive patterns
Strategy: validation
Validate before calling
fn is_bare_skill_name(name: &str) -> bool {
!name.is_empty() && !name.contains("..") && !name.contains('/') && !name.contains('\\')
}
if !is_bare_skill_name(&name) {
eprintln!("rejecting skill name {name:?}: pass a bare name plus --bundle/--agent");
} Type guard
fn is_bare_skill_name(name: &str) -> bool {
!name.is_empty() && !name.contains("..") && !name.contains('/') && !name.contains('\\')
} Prevention
- Never build the name argument from user or network input without the bare-name check
- Use --bundle/--agent for scoping, never path fragments
- Quote skill names in shell scripts to prevent tab-completion or glob insertion of slashes
When it happens
Trigger: Passing a nested path (`skills remove bundles/foo`), a traversal string (`skills remove ../../somewhere`), or a Windows-style path; shell tab-completion inserting a slash into the name.
Common situations: Users used to path-based CLIs; older scripts written against a path-style interface; untrusted input forwarded verbatim from an external source into the command.
Related errors
- skill '{$skill}' in {$url} resolves outside the cloned catal
- skill catalog {$url} has a skills/ directory that resolves o
- skill '{$skill}' in {$url} is a symlink; catalog skills must
- cli-skills-audit-failed
- Skill path escapes skills directory: {name}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/5d89dbc51c4d6287.
Report an issue: GitHub.