{"record":{"id":"5fa2823a5def88c0","repo":"Hmbown/CodeWhale","slug":"sub-agent-state-path-must-include-a-parent-directo","errorCode":null,"errorMessage":"sub-agent state path must include a parent directory","messagePattern":"sub-agent state path must include a parent directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/mod.rs","lineNumber":7683,"sourceCode":"        &Path::new(\".codewhale\")\n            .join(\"state\")\n            .join(SUBAGENT_STATE_FILE),\n    )\n}\n\nfn checked_subagent_state_path(state_root: &Path, path: &Path) -> Result<PathBuf> {\n    let state_root = normalize_subagent_workspace(state_root);\n    let absolute = if path.is_absolute() {\n        path.to_path_buf()\n    } else {\n        state_root.join(path)\n    };\n    let file_name = absolute\n        .file_name()\n        .ok_or_else(|| anyhow!(\"sub-agent state path must include a file name\"))?;\n    let parent = absolute\n        .parent()\n        .ok_or_else(|| anyhow!(\"sub-agent state path must include a parent directory\"))?;\n    let parent = match parent.canonicalize() {\n        Ok(parent) => parent,\n        Err(err) if err.kind() == std::io::ErrorKind::NotFound => normalize_path_components(parent),\n        Err(err) => return Err(err.into()),\n    };\n    let state_path = parent.join(file_name);\n    if !state_path.starts_with(&state_root) {\n        return Err(anyhow!(\n            \"sub-agent state path must stay within state root: {}\",\n            state_path.display()\n        ));\n    }\n    reject_root_relative_symlinks(&state_root, &state_path)?;\n    Ok(state_path)\n}\n\nfn normalize_subagent_workspace(workspace: &Path) -> PathBuf {\n    if let Ok(canonical) = workspace.canonicalize() {","sourceCodeStart":7665,"sourceCodeEnd":7701,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/subagent/mod.rs#L7665-L7701","documentation":"`checked_subagent_state_path` requires a parent directory component after extracting the file name; parent() returning None means the path is degenerate (no directory part above the file). In practice this is nearly unreachable when paths are joined under an absolute state root, and it exists as a completeness guard.","triggerScenarios":"A path object with a file name but no parent component — essentially only constructible from degenerate inputs (empty/odd PathBuf built outside the manager's join logic).","commonSituations":"Direct API use with hand-constructed Path values bypassing state_root.join; test code assembling unusual paths.","solutions":["Always derive sub-agent state paths via state_root.join(relative) rather than constructing bare Path values.","Reject empty or component-less path strings at the input boundary."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fn has_parent(rel: &str) -> bool {\n    std::path::Path::new(rel).parent().map_or(false, |p| !p.as_os_str().is_empty())\n}","typeGuard":"fn is_joinable_under_root(rel: &str) -> bool {\n    has_parent(rel) && has_file_name(rel)\n}","tryCatchPattern":"match checked_subagent_state_path(&state_root, Path::new(rel)) {\n    Err(e) if e.to_string().contains(\"parent directory\") => { /* rebuild path under an explicit directory */ }\n    r => r?,\n}","preventionTips":["Never construct state Path values by hand; always join under the state root."],"tags":["subagent","path-validation","rust"],"backgroundTag":"invalid-file-path","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}