{"record":{"id":"30a80e2f851a2291","repo":"Hmbown/CodeWhale","slug":"invalid-on-disk-package-directory-for-skill","errorCode":null,"errorMessage":"invalid on-disk package directory for skill '{}'","messagePattern":"invalid on-disk package directory for skill '(.+?)'","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/skills/mutation.rs","lineNumber":547,"sourceCode":"        .into_iter()\n        .find(|s| &s.id == skill_id)\n        .with_context(|| format!(\"audited skill {} not found\", skill_id.canonical_name))?;\n    let path = skill.root.path.join(&skill.id.relative_dir);\n    Ok((skill, path))\n}\n\n/// Directory segment under the skills root. Prefer this over `canonical_name`\n/// when calling install helpers that join `skills_dir / name` — installs use\n/// raw frontmatter names, while audit stores a normalized lookup key.\nfn on_disk_package_name(skill_id: &AuditedSkillId) -> Result<&str> {\n    let mut components = skill_id.relative_dir.components();\n    let name = match (components.next(), components.next()) {\n        (Some(Component::Normal(name)), None) => name.to_str(),\n        _ => None,\n    }\n    .filter(|name| !name.is_empty())\n    .ok_or_else(|| {\n        anyhow::anyhow!(\n            \"invalid on-disk package directory for skill '{}'\",\n            skill_id.canonical_name\n        )\n    })?;\n    Ok(name)\n}\n\nfn verify_expected_digest(path: &Path, expected: Option<&str>) -> Result<Option<String>> {\n    let current = package_digest::compute_package_digest(path)\n        .with_context(|| format!(\"cannot digest {}\", path.display()))?;\n    if let Some(expected) = expected\n        && expected != current\n    {\n        bail!(\n            \"skill content changed since audit (expected {expected}, found {current}); \\\n             re-review before mutating\"\n        );\n    }","sourceCodeStart":529,"sourceCodeEnd":565,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/skills/mutation.rs#L529-L565","documentation":"on_disk_package_name derives the on-disk package directory from an audited skill's relative_dir: it must consist of exactly one normal path component (the folder sits directly under the skills root, e.g. 'my-skill'). The error fires when relative_dir is empty, '.', '..', the root itself, has multiple components ('a/b'), or is not valid UTF-8. The message reports skill_id.canonical_name, which may differ from the directory name because the audit index stores a normalized lookup key while installs use raw frontmatter names.","triggerScenarios":"Calling an install/mutation helper with an AuditedSkillId whose relative_dir is not a single flat segment: audit entries produced by an older nested layout ('pack/skill'), a manually reorganized skills directory, or a stale audit cache whose relative paths no longer match the flat install layout.","commonSituations":"Skills directory reorganized by hand (nested folders) after the audit ran; audit index from an older version; a skill dir renamed or moved so the cached relative_dir resolves to root.","solutions":["Re-run the skills audit/scan so the index rebuilds relative_dir against the current flat layout.","Ensure each skill package is a single directory directly under the skills root.","Remove stale audit entries for skills that no longer exist on disk.","If the directory name contains non-UTF-8 bytes, rename it to a UTF-8 name and re-audit."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"use std::path::{Component, Path};\n\nfn is_flat_skill_dir(relative_dir: &Path) -> bool {\n    let mut components = relative_dir.components();\n    matches!((components.next(), components.next()),\n        (Some(Component::Normal(_)), None))\n}\n\nif !is_flat_skill_dir(&skill_id.relative_dir) {\n    anyhow::bail!(\"skill '{}' is not a flat directory under the skills root\", skill_id.canonical_name);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep every skill package as one directory directly under the skills root — no nesting.","Re-run the skills audit after moving, renaming, or reorganizing skill directories.","Use only ASCII/UTF-8 directory names for skill packages."],"tags":["skills","filesystem","audit","path-validation"],"backgroundTag":"path-validation-failed","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}