gitbutlerapp/gitbutler · info

skill format path components are never empty

Error message

skill format path components are never empty

What it means

`but skill install` copies embedded skill files into agent-format-specific directories. Each SkillFormat carries a static path_components slice; skills_parent_dir() splits off the last component and expects the slice to be non-empty. All formats are compile-time constants with at least one component (skill/mod.rs:66-87 and the SkillFormat table), so the panic fires only if a build registers a SkillFormat with an empty path_components.

Source

Thrown at crates/but/src/command/skill/mod.rs:136

        Self {
            name,
            description: "Agent-specific global skill format",
            availability: SkillFormatAvailability::GlobalOnly,
            path_components,
        }
    }

    /// Get the actual installation path given a base directory
    fn get_install_path(&self, base_dir: &std::path::Path) -> PathBuf {
        join_relative_path(base_dir, self.path_components)
    }

    /// The skills directory that holds this format's installation folder.
    fn skills_parent_dir(&self, base_dir: &std::path::Path) -> PathBuf {
        let (_, parent) = self
            .path_components
            .split_last()
            .expect("skill format path components are never empty");
        join_relative_path(base_dir, parent)
    }

    fn is_available_for(&self, global: bool) -> bool {
        matches!(
            (global, self.availability),
            (_, SkillFormatAvailability::LocalAndGlobal)
                | (false, SkillFormatAvailability::LocalOnly)
                | (true, SkillFormatAvailability::GlobalOnly)
        )
    }
}

/// Install-path components (relative to a base directory) for a skill format,
/// selected by its display name and whether the install is global. This keeps
/// callers outside `but skill` — e.g. the `agent setup` wizard — installing to
/// the same locations that `but skill check`/install/update discover, instead of
/// duplicating (and drifting from) these paths.

View on GitHub (pinned to 2497b8007a)

Solutions

  1. Give the new format at least one path component (its install folder relative to the skills base dir)
  2. Enforce it at construction: assert !path_components.is_empty() in SkillFormat::global/new so bad constants fail at startup or test time, not at install time
  3. Optionally degrade: treat empty components as 'install directly into base_dir' via unwrap_or rather than panicking

Example fix

// before — empty slice slips through to a render-time panic
const fn global(name: &'static str, path_components: &'static [&'static str]) -> Self { Self { name, path_components, .. } }

// after — fail at construction, not in skills_parent_dir
const fn global(name: &'static str, path_components: &'static [&'static str]) -> Self {
    assert!(!path_components.is_empty(), "skill format needs an install path");
    Self { name, path_components, .. }
}
Defensive patterns

Strategy: validation

Validate before calling

// static sanity check for the format table, in a unit test
#[test]
fn skill_formats_have_paths() {
    for format in SKILL_FORMATS {
        assert!(!format.path_components.is_empty(), "format {} has no install path", format.name);
    }
}

Prevention

When it happens

Trigger: Running `but skill install` (or computing install paths) on a custom build where a newly added SkillFormat was defined with path_components: &[] (e.g. a newly supported agent format with a missing directory).

Common situations: Contributions adding a new agent skill format and forgetting the install directory; refactoring the static format table through a macro/helper that can emit empty slices.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@2497b8007a (2026-08-17). Data as JSON: /api/errors/6415313f611c736f. Report an issue: GitHub.