{"record":{"id":"18cf283d8518026e","repo":"Hmbown/CodeWhale","slug":"sub-agent-transcript-artifact-must-have-a-parent-d","errorCode":null,"errorMessage":"sub-agent transcript artifact must have a parent directory","messagePattern":"sub-agent transcript artifact must have a parent directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/subagent/mod.rs","lineNumber":7794,"sourceCode":"    use std::os::unix::fs::OpenOptionsExt;\n\n    fs::OpenOptions::new()\n        .read(true)\n        .custom_flags(libc::O_NOFOLLOW)\n        .open(path)\n        .map_err(Into::into)\n}\n\n#[cfg(not(unix))]\nfn open_subagent_state_file(path: &Path) -> Result<fs::File> {\n    fs::File::open(path).map_err(Into::into)\n}\n\nfn prepare_subagent_transcript_parent(state_root: &Path, path: &Path) -> Result<()> {\n    reject_root_relative_symlinks(state_root, path)?;\n    let parent = path\n        .parent()\n        .ok_or_else(|| anyhow!(\"sub-agent transcript artifact must have a parent directory\"))?;\n    fs::create_dir_all(parent)?;\n    // Re-check after creation so a pre-existing component cannot redirect the\n    // private transcript outside the state root.\n    reject_root_relative_symlinks(state_root, path)?;\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::PermissionsExt;\n        fs::set_permissions(parent, fs::Permissions::from_mode(0o700))?;\n    }\n    Ok(())\n}\n\nfn create_private_subagent_transcript(state_root: &Path, path: &Path, bytes: &[u8]) -> Result<()> {\n    prepare_subagent_transcript_parent(state_root, path)?;\n    let mut file = open_private_subagent_transcript(path, false)?;\n    file.write_all(bytes)?;\n    file.sync_all()?;\n    Ok(())","sourceCodeStart":7776,"sourceCodeEnd":7812,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/subagent/mod.rs#L7776-L7812","documentation":"`prepare_subagent_transcript_parent` requires the artifact path to have a parent directory (path.parent()); a degenerate path (empty or root-like) has none, so transcript creation is refused before any directory is made. Like 1211 this guards against malformed path construction rather than runtime state.","triggerScenarios":"Building a transcript path from user/model input that collapses to an empty or root-level path, or a PathBuf constructed without joining under the state root.","commonSituations":"Templating that yields \"\" for the directory part; API misuse calling the persister with a bare file name string.","solutions":["Construct artifact paths as state_root.join(transcripts_dir).join(file_name) with both parts non-empty.","Reject empty directory components at the input boundary."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let artifact = state_root.join(\"transcripts\").join(format!(\"{agent_id}.jsonl\"));\nassert!(artifact.parent().is_some(), \"artifact path needs a directory part\");","typeGuard":"fn path_has_parent(p: &Path) -> bool {\n    p.parent().map_or(false, |parent| !parent.as_os_str().is_empty())\n}","tryCatchPattern":"match prepare_subagent_transcript_parent(&state_root, &path) {\n    Err(e) if e.to_string().contains(\"parent directory\") => { /* rebuild path under an explicit transcripts dir */ }\n    r => r?,\n}","preventionTips":["Always build artifact paths as root.join(dir).join(file), both parts non-empty."],"tags":["subagent","transcript","path-validation","rust"],"backgroundTag":"invalid-file-path","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}