Hmbown/CodeWhale · error

state subdir must not contain parent-dir (..) components

Error message

state subdir must not contain parent-dir (..) components: {subdir}

What it means

Parent-directory (`..`) components in a state subdir would let callers traverse out of the state root. `ensure_safe_state_subdir` rejects any subdir containing `Component::ParentDir`, even nested relative paths like `"a/b"` remain allowed.

Solutions

  1. Use only `Normal` path components relative to the state root (nested `"a/b"` is fine)
  2. Canonicalize the target and recompute it relative to the state root before calling
  3. Use the explicit state-root override API if a different root location is genuinely needed

Example fix

// before
let dir = state_dir_in("../other-tool-state")?;
// after
let dir = state_dir_in("sessions")?;
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

fn contained_subdir(subdir: &str) -> Option<&str> {
    let p = Path::new(subdir);
    if subdir.is_empty()
        || p.components().any(|c| !matches!(c, Component::Normal(_)))
    { None } else { Some(subdir) }
}

Try / catch

match state_dir_in(subdir) {
    Err(e) if e.to_string().contains("parent-dir") => {
        // reject or recompute the path within the state root
    }
    result => result,
}

Prevention

When it happens

Trigger: Calling a state-path helper with a subdir containing `..` segments, e.g. `"../shared"` or `"sessions/../../etc"`.

Common situations: Constructing paths with `..` to reach a sibling directory of the state root; user-supplied relative paths passed through unvalidated; template strings with `..` placeholders left unsubstituted.

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/2c09ec007feb71ac. Report an issue: GitHub.

Appendix: source

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

        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)?;
    let explicit_codewhale_home = codewhale_home_is_explicit();
    let primary = codewhale_home()?.join(subdir);
    if explicit_codewhale_home || primary.exists() {
        return Ok(primary);
    }
    let legacy = legacy_deepseek_home()?.join(subdir);

View on GitHub (pinned to 73e0f67d83)