Hmbown/CodeWhale · error

skill version marker should have a parent directory

Error message

skill version marker should have a parent directory

What it means

Panic in the atomic skill version-marker writer. `Path::parent()` returns `None` only when the path is empty or the filesystem root (`/`), so the expect fires when the composed marker path degenerated to `""` or `"/"` — e.g. an empty state-dir base joined with nothing — before the `NamedTempFile::new_in` call can run.

Source

Thrown at crates/tui/src/skills/system.rs:474

/// are preserved.
fn retire_unchanged_v4_best_practices(skills_dir: &Path) -> std::io::Result<bool> {
    let dir = skills_dir.join("v4-best-practices");
    let file = dir.join("SKILL.md");
    if !file.exists() {
        return Ok(false);
    }
    let existing = fs::read_to_string(&file)?;
    if existing != v4_best_practices_body() {
        return Ok(false);
    }
    fs::remove_dir_all(&dir)?;
    Ok(true)
}

fn write_marker_atomically(marker: &Path, version: &str) -> std::io::Result<()> {
    let parent = marker
        .parent()
        .expect("skill version marker should have a parent directory");
    let mut temporary = tempfile::NamedTempFile::new_in(parent)?;
    temporary.write_all(version.as_bytes())?;
    temporary.as_file().sync_all()?;
    // `rename` atomically replaces a file on Unix. Windows refuses to replace
    // an existing destination, so remove only this reserved marker first.
    #[cfg(windows)]
    if marker.exists() {
        fs::remove_file(marker)?;
    }
    fs::rename(temporary.path(), marker)
}

#[cfg(test)]
mod tests;

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Trace where the marker path is composed (skills state dir) and guard: reject empty or root paths before calling `write_marker_atomically`.
  2. Fix the override/config value that produced the degenerate path.
  3. Return `Err(io::ErrorKind::InvalidInput)` naming the bad path instead of `expect`.
  4. Add a test with an empty marker path asserting the graceful error.

Example fix

// before
let parent = marker.parent().expect("skill version marker should have a parent directory");

// after: reject degenerate paths with a real error
let Some(parent) = marker.parent() else {
    return Err(std::io::Error::new(
        std::io::ErrorKind::InvalidInput,
        format!("marker path `{}` has no parent directory", marker.display()),
    ));
};
Defensive patterns

Strategy: validation

Validate before calling

fn marker_path_ok(marker: &std::path::Path) -> bool {
    !marker.as_os_str().is_empty()
        && marker != std::path::Path::new("/")
        && marker.parent().is_some()
}

Prevention

When it happens

Trigger: A marker path built from an empty string (misconfigured/empty state-dir override, `PathBuf::from("")` flowing through) or literally `"/"`; the first skill-system versioning write then panics at system.rs:474.

Common situations: Env/config overrides for the codewhale state dir set to empty; tests constructing marker paths from `String::new()`; path-joining code that drops the filename component under some condition.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/7bbea85718956046. Report an issue: GitHub.