{"record":{"id":"a048162566817377","repo":"xai-org/grok-build","slug":"target-has-no-parent","errorCode":null,"errorMessage":"target has no parent","messagePattern":"target has no parent","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-shell/src/session/workflow/registry.rs","lineNumber":616,"sourceCode":"    }\n\n    let canonical = dunce::canonicalize(dir).map_err(|error| ResolveError::Io {\n        path: dir.display().to_string(),\n        error: error.to_string(),\n    })?;\n    if !canonical.starts_with(&root) {\n        return Err(ResolveError::UntrustedPath {\n            path: dir.display().to_string(),\n            reason: \"save directory escaped project root\".into(),\n        });\n    }\n    Ok(())\n}\n\nfn atomic_create_new(target: &Path, bytes: &[u8]) -> io::Result<()> {\n    let parent = target\n        .parent()\n        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, \"target has no parent\"))?;\n    let temp = parent.join(format!(\".workflow-{}.tmp\", uuid::Uuid::now_v7().simple()));\n    let result = (|| {\n        let mut options = std::fs::OpenOptions::new();\n        options.write(true).create_new(true);\n        #[cfg(unix)]\n        {\n            use std::os::unix::fs::OpenOptionsExt;\n            options.mode(0o600).custom_flags(libc::O_NOFOLLOW);\n        }\n        let mut file = options.open(&temp)?;\n        file.write_all(bytes)?;\n        file.sync_all()?;\n        #[cfg(unix)]\n        std::fs::hard_link(&temp, target)?;\n        #[cfg(windows)]\n        atomic_rename_noreplace_windows(&temp, target)?;\n        if let Ok(dir) = std::fs::File::open(parent) {\n            let _ = dir.sync_all();","sourceCodeStart":598,"sourceCodeEnd":634,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-shell/src/session/workflow/registry.rs#L598-L634","documentation":"atomic_create_new writes a new workflow file by creating a temp file in the target's parent directory; if Path::parent() returns None the path has no parent component (e.g. a bare relative filename like \"wf.json\" or a root path), and the library raises InvalidInput \"target has no parent\". It cannot stage a temp sibling file without a directory.","triggerScenarios":"Calling save_project_workflow with a target path that has no parent component — a bare filename relative to nothing, or a path consisting only of a root (e.g. \"/\" on unix).","commonSituations":"Constructing the workflow path from an unvalidated user/config string; forgetting to join the registry/scratch directory onto a workflow id; passing Path::new(\"workflow.json\") instead of dir.join(\"workflow.json\").","solutions":["Join the target onto the intended directory before saving: base_dir.join(file_name)","Validate the path with target.parent().is_some() before calling","Canonicalize/resolve relative paths against the project root early","Reject empty or root-only path components at config-parse time"],"exampleFix":"// before\nlet target = Path::new(&workflow_id).with_extension(\"json\");\nregistry.save_project_workflow(&target, &bytes)?;\n// after\nlet target = workflows_dir.join(workflow_id).with_extension(\"json\");\nassert!(target.parent().is_some(), \"target must have a parent dir\");\nregistry.save_project_workflow(&target, &bytes)?;","handlingStrategy":"validation","validationCode":"fn ensure_parent(target: &Path) -> io::Result<()> {\n    if target.parent().is_none() {\n        return Err(io::Error::new(io::ErrorKind::InvalidInput, \"target has no parent\"));\n    }\n    std::fs::create_dir_all(target.parent().unwrap())\n}","typeGuard":"fn has_parent(target: &Path) -> bool {\n    target.parent().is_some()\n}","tryCatchPattern":"match registry.save_project_workflow(&target, &bytes) {\n    Err(e) if e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains(\"no parent\") => {\n        eprintln!(\"workflow path must include a directory: {target:?}\");\n    }\n    other => other?,\n}","preventionTips":["Always build workflow paths as dir.join(id), never from a bare id string","Validate user/config-supplied paths contain a directory component at load time","Canonicalize relative paths against the project root early","Create parent directories with create_dir_all before saving"],"tags":["io","path","validation","atomic-write"],"backgroundTag":"invalid-file-path","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}