{"record":{"id":"c1b62f0374d0369a","repo":"xai-org/grok-build","slug":"workflow-path-has-no-parent","errorCode":null,"errorMessage":"workflow path has no parent","messagePattern":"workflow path has no parent","errorType":"validation","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-shell/src/session/workflow/store.rs","lineNumber":372,"sourceCode":"}\n\nfn atomic_write_new(path: &Path, bytes: &[u8]) -> io::Result<()> {\n    if path.exists() {\n        return Err(io::Error::new(\n            io::ErrorKind::AlreadyExists,\n            format!(\"immutable workflow file already exists: {}\", path.display()),\n        ));\n    }\n    atomic_write(path, bytes, false)\n}\n\nfn atomic_write_replace(path: &Path, bytes: &[u8]) -> io::Result<()> {\n    atomic_write(path, bytes, true)\n}\n\nfn atomic_write(path: &Path, bytes: &[u8], replace: bool) -> io::Result<()> {\n    let parent = path.parent().ok_or_else(|| {\n        io::Error::new(io::ErrorKind::InvalidInput, \"workflow path has no parent\")\n    })?;\n    std::fs::create_dir_all(parent)?;\n    let file_name = path\n        .file_name()\n        .and_then(|name| name.to_str())\n        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, \"workflow path is not UTF-8\"))?;\n    let tmp = parent.join(format!(\n        \".{file_name}.{}.{}.tmp\",\n        std::process::id(),\n        uuid::Uuid::now_v7().simple()\n    ));\n    let result = (|| {\n        let mut file = std::fs::OpenOptions::new()\n            .write(true)\n            .create_new(true)\n            .open(&tmp)?;\n        file.write_all(bytes)?;\n        file.sync_all()?;","sourceCodeStart":354,"sourceCodeEnd":390,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-shell/src/session/workflow/store.rs#L354-L390","documentation":"atomic_write needs the parent directory of the destination path to create the temp file and rename. If path.parent() returns None (the path is a bare file name with no directory component), it returns InvalidInput with this message.","triggerScenarios":"Passing a relative bare name like \"runs.json\" (no directory) to atomic_write via atomic_write_new/atomic_write_replace; constructing a path with Path::new(file_name) instead of joining it to the runs dir.","commonSituations":"Refactor changed an absolute path into a bare file name; a config value holding just a filename was passed where a full path is required; misuse of Path::file_name output fed back as a path.","solutions":["Always pass fully-qualified paths that include the parent directory (e.g. run_dir.join(\"runs.json\"))","Fix callers to build paths from the configured runs root, not bare names","Verify path construction with .parent().is_some() before calling store write APIs"],"exampleFix":"// before\nlet path = Path::new(\"runs.json\"); // no parent\n// after\nlet path = Path::new(RUNS_ROOT).join(\"run-abc\").join(\"runs.json\");","handlingStrategy":"validation","validationCode":"fn writable_path(p: &std::path::Path) -> bool {\n    p.parent().map(|parent| !parent.as_os_str().is_empty()).unwrap_or(false)\n}","typeGuard":"fn with_parent(p: &std::path::Path) -> Option<&std::path::Path> {\n    p.parent().filter(|parent| !parent.as_os_str().is_empty())\n}","tryCatchPattern":"let path = build_full_path(&run_dir, \"runs.json\");\nassert!(path.parent().is_some(), \"workflow paths must include a parent dir\");\natomic_write_replace(&path, &bytes)?;","preventionTips":["Always join artifact names onto the runs root: runs_root.join(run_id).join(name)","Never pass bare file names or Path::file_name() results as destinations","Add a debug assertion that destination paths are absolute or root-relative","Cover path construction with unit tests using Path::new(\"file.json\") as a negative case"],"tags":["io","invalid-input","path"],"backgroundTag":"invalid-path-no-parent","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}