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

  1. Pass only the skill's directory name: `skills remove foo`
  2. Scope the search with --bundle <alias> or --agent <alias> instead of embedding a path in the name
  3. Quote the argument so the shell neither expands nor splits it
  4. 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

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/5d89dbc51c4d6287. Report an issue: GitHub.