Hmbown/CodeWhale · error

state subdir must not contain a root or prefix

Error message

state subdir must not contain a root or prefix: {subdir}

What it means

Beyond plain absoluteness, a subdir containing a root (`/`) or platform prefix (`C:\`, UNC) component could redirect resolution outside the state root. `ensure_safe_state_subdir` inspects `Path::components()` and rejects any `RootDir` or `Prefix` component.

Solutions

  1. Pass a plain relative single component like `"sessions"`
  2. Normalize the input with a relative-path join against the state root before calling
  3. Strip leading separators and drive prefixes, or reject the input in your own validation first

Example fix

// before
let dir = state_dir_in("/sessions")?;
// after
let dir = state_dir_in("sessions")?;
Defensive patterns

Strategy: validation

Validate before calling

use std::path::{Component, Path};
fn no_root_or_prefix(subdir: &str) -> bool {
    !Path::new(subdir).components()
        .any(|c| matches!(c, Component::RootDir | Component::Prefix(_)))
}

Try / catch

match state_dir_in(subdir) {
    Err(e) if e.to_string().contains("root or prefix") => {
        // strip leading separator / drive prefix and retry
    }
    result => result,
}

Prevention

When it happens

Trigger: Calling a state-path helper with a subdir that parses into `Component::RootDir` or `Component::Prefix(_)` — e.g. `"/sessions"`, `"C:sessions"`, or `"\\\\server\\share"`.

Common situations: Windows paths built with drive letters leaking into the subdir slot; strings that begin with a separator; paths copied verbatim from filesystem browsing UIs.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/d008072916fafb4b. Report an issue: GitHub.

Appendix: source

Thrown at crates/config/src/lib.rs:5735

/// subdir string; every in-tree caller passes a hardcoded single component
/// (e.g. `"sessions"`, `"."`). This validates defensively so a future caller
/// can never traverse out of the state root via `..` components or an absolute
/// path. Nested relative paths such as `"a/b"` are permitted.
fn ensure_safe_state_subdir(subdir: &str) -> Result<()> {
    if subdir.is_empty() {
        bail!("state subdir must not be empty");
    }
    let path = std::path::Path::new(subdir);
    if path.is_absolute() {
        bail!("state subdir must not be an absolute path: {subdir}");
    }
    if path.components().any(|c| {
        matches!(
            c,
            std::path::Component::RootDir | std::path::Component::Prefix(_)
        )
    }) {
        bail!("state subdir must not contain a root or prefix: {subdir}");
    }
    if path
        .components()
        .any(|c| matches!(c, std::path::Component::ParentDir))
    {
        bail!("state subdir must not contain parent-dir (..) components: {subdir}");
    }
    Ok(())
}

/// Resolve a state subdirectory, preferring the CodeWhale root if
/// it already exists, otherwise falling back to the legacy root.
///
/// This is the read-path resolver: it returns the primary path when
/// migration has occurred or on a fresh install, but keeps reading
/// from the legacy path for users who haven't migrated yet.
pub fn resolve_state_dir(subdir: &str) -> Result<PathBuf> {
    ensure_safe_state_subdir(subdir)?;

View on GitHub (pinned to 73e0f67d83)