{"record":{"id":"6415313f611c736f","repo":"gitbutlerapp/gitbutler","slug":"skill-format-path-components-are-never-empty","errorCode":null,"errorMessage":"skill format path components are never empty","messagePattern":"skill format path components are never empty","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"info","filePath":"crates/but/src/command/skill/mod.rs","lineNumber":136,"sourceCode":"        Self {\n            name,\n            description: \"Agent-specific global skill format\",\n            availability: SkillFormatAvailability::GlobalOnly,\n            path_components,\n        }\n    }\n\n    /// Get the actual installation path given a base directory\n    fn get_install_path(&self, base_dir: &std::path::Path) -> PathBuf {\n        join_relative_path(base_dir, self.path_components)\n    }\n\n    /// The skills directory that holds this format's installation folder.\n    fn skills_parent_dir(&self, base_dir: &std::path::Path) -> PathBuf {\n        let (_, parent) = self\n            .path_components\n            .split_last()\n            .expect(\"skill format path components are never empty\");\n        join_relative_path(base_dir, parent)\n    }\n\n    fn is_available_for(&self, global: bool) -> bool {\n        matches!(\n            (global, self.availability),\n            (_, SkillFormatAvailability::LocalAndGlobal)\n                | (false, SkillFormatAvailability::LocalOnly)\n                | (true, SkillFormatAvailability::GlobalOnly)\n        )\n    }\n}\n\n/// Install-path components (relative to a base directory) for a skill format,\n/// selected by its display name and whether the install is global. This keeps\n/// callers outside `but skill` — e.g. the `agent setup` wizard — installing to\n/// the same locations that `but skill check`/install/update discover, instead of\n/// duplicating (and drifting from) these paths.","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/2497b8007aa4a1922dae9a805b32ffe5b5037785/crates/but/src/command/skill/mod.rs#L118-L154","documentation":"`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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Give the new format at least one path component (its install folder relative to the skills base dir)","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","Optionally degrade: treat empty components as 'install directly into base_dir' via unwrap_or rather than panicking"],"exampleFix":"// before — empty slice slips through to a render-time panic\nconst fn global(name: &'static str, path_components: &'static [&'static str]) -> Self { Self { name, path_components, .. } }\n\n// after — fail at construction, not in skills_parent_dir\nconst fn global(name: &'static str, path_components: &'static [&'static str]) -> Self {\n    assert!(!path_components.is_empty(), \"skill format needs an install path\");\n    Self { name, path_components, .. }\n}","handlingStrategy":"validation","validationCode":"// static sanity check for the format table, in a unit test\n#[test]\nfn skill_formats_have_paths() {\n    for format in SKILL_FORMATS {\n        assert!(!format.path_components.is_empty(), \"format {} has no install path\", format.name);\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Every SkillFormat must declare at least one install-path component; review new formats against the existing table","When adding support for a new agent's skill layout, copy an existing entry (name + dir components) and adjust","Assert non-emptiness at construction time so a bad constant fails at startup, not at `but skill install`"],"tags":["rust","invariant","skill-install","static-config","cli"],"backgroundTag":"internal-invariant-panic","analyzedSha":"2497b8007aa4a1922dae9a805b32ffe5b5037785","analyzedAt":"2026-08-17T00:30:25.648Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}