Hmbown/CodeWhale · error

sub-agent state path must stay within state root: {}

Error message

sub-agent state path must stay within state root: {}

What it means

`checked_subagent_state_path` canonicalizes (or component-normalizes) the parent directory, rejoins the file name, and requires the result to start with the normalized state root. This error means the fully resolved path escapes the state root — typically ".." traversal or an absolute parent outside the root. It is a fail-closed path-containment guard against model-supplied or tampered paths.

Source

Thrown at crates/tui/src/tools/subagent/mod.rs:7691

    let absolute = if path.is_absolute() {
        path.to_path_buf()
    } else {
        state_root.join(path)
    };
    let file_name = absolute
        .file_name()
        .ok_or_else(|| anyhow!("sub-agent state path must include a file name"))?;
    let parent = absolute
        .parent()
        .ok_or_else(|| anyhow!("sub-agent state path must include a parent directory"))?;
    let parent = match parent.canonicalize() {
        Ok(parent) => parent,
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => normalize_path_components(parent),
        Err(err) => return Err(err.into()),
    };
    let state_path = parent.join(file_name);
    if !state_path.starts_with(&state_root) {
        return Err(anyhow!(
            "sub-agent state path must stay within state root: {}",
            state_path.display()
        ));
    }
    reject_root_relative_symlinks(&state_root, &state_path)?;
    Ok(state_path)
}

fn normalize_subagent_workspace(workspace: &Path) -> PathBuf {
    if let Ok(canonical) = workspace.canonicalize() {
        return canonical;
    }
    let absolute = if workspace.is_absolute() {
        workspace.to_path_buf()
    } else {
        std::env::current_dir()
            .unwrap_or_else(|_| PathBuf::from("."))
            .join(workspace)

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Strip leading ../ and reject any relative path that cannot be contained under the state root before calling.
  2. Always pass paths relative to the manager's state root; never absolute external paths.
  3. Treat the error as hostile-input signal: audit where the path string came from rather than just fixing the path.

Example fix

// before
checked_subagent_state_path(root, Path::new("../../etc/passwd"))?; // Err 1212

// after: sanitize to a contained relative path
let rel = rel.replace('..', "_");
checked_subagent_state_path(root, &root.join(rel))?;
Defensive patterns

Strategy: validation

Validate before calling

fn contained_rel_path(state_root: &Path, rel: &str) -> Option<PathBuf> {
    let p = state_root.join(rel);
    let norm = p.components().collect::<PathBuf>(); // squashes ..
    norm.starts_with(state_root).then_some(norm)
}

Type guard

fn is_contained(state_root: &Path, candidate: &Path) -> bool {
    candidate.starts_with(state_root)
        && !candidate.components().any(|c| matches!(c, std::path::Component::ParentDir))
}

Try / catch

match checked_subagent_state_path(&state_root, path) {
    Err(e) if e.to_string().contains("within state root") => { /* treat as hostile input: reject and audit source */ }
    r => r?,
}

Prevention

When it happens

Trigger: A relative path with ../ segments that resolves above the state root; an absolute path pointing elsewhere on disk; a parent that canonicalizes (via existing prefix) outside the root.

Common situations: Prompt-injected or hallucinated paths from a model; state dirs relocated while old absolute paths persisted; tools forwarding user input unfiltered.

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@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/5486c1195450cc41. Report an issue: GitHub.