{"record":{"id":"f1adf901fde4c58d","repo":"BloopAI/vibe-kanban","slug":"cannot-atomically-write-path-without-a-parent-dire","errorCode":null,"errorMessage":"cannot atomically write path without a parent directory","messagePattern":"cannot atomically write path without a parent directory","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/desktop-bridge/src/ssh_config.rs","lineNumber":117,"sourceCode":"    if existing.contains(include_line) {\n        return Ok(());\n    }\n\n    // Prepend the Include directive (SSH config is first-match)\n    let new_content = format!(\"{include_line}\\n{existing}\");\n    atomic_write_text_file(&config_path, &new_content)?;\n\n    Ok(())\n}\n\nfn vk_ssh_dir() -> Result<PathBuf, DesktopBridgeError> {\n    let home = dirs::home_dir().ok_or(DesktopBridgeError::NoHomeDirectory)?;\n    Ok(home.join(\".vk-ssh\"))\n}\n\nfn atomic_write_text_file(path: &Path, content: &str) -> Result<(), DesktopBridgeError> {\n    let parent = path.parent().ok_or_else(|| {\n        io::Error::new(\n            io::ErrorKind::InvalidInput,\n            \"cannot atomically write path without a parent directory\",\n        )\n    })?;\n    fs::create_dir_all(parent)?;\n\n    let nonce = SystemTime::now()\n        .duration_since(UNIX_EPOCH)\n        .map(|d| d.as_nanos())\n        .unwrap_or(0);\n    let file_name = path\n        .file_name()\n        .and_then(|n| n.to_str())\n        .unwrap_or(\"config\");\n    let tmp_name = format!(\".{file_name}.tmp-{}-{nonce}\", std::process::id());\n    let tmp_path = parent.join(tmp_name);\n\n    let mut tmp_file = fs::OpenOptions::new()","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/desktop-bridge/src/ssh_config.rs#L99-L135","documentation":"atomic_write_text_file writes a file by creating a temp file in the path's parent directory and renaming it into place. Path::parent() returns None only for paths with no directory component (e.g. a bare relative filename like \"config\"), so this error signals a malformed destination path, not a missing directory (that's handled by create_dir_all).","triggerScenarios":"Calling update_ssh_config or ensure_ssh_include when a computed destination path is a bare filename with no parent component (e.g. passing \"vk-ssh-config\" instead of \"/home/user/.vk-ssh/vk-ssh-config\").","commonSituations":"Overriding the home directory lookup so config_dir() returns \"\"; passing a relative single-segment path through custom configuration; constructing paths from an empty string env var.","solutions":["Pass an absolute path that includes a directory component, e.g. ~/.vk-ssh/<file>.","Check any env var or setting overriding the config location — an empty value collapses the path to a bare filename.","Ensure dirs::home_dir() is resolving (the same function errors earlier with NoHomeDirectory if not).","Build the path via join on a base directory rather than concatenating strings."],"exampleFix":"// before\natomic_write_text_file(Path::new(\"vk-ssh-config\"), content)?; // no parent\n// after\nlet path = dirs::home_dir().unwrap().join(\".vk-ssh\").join(\"vk-ssh-config\");\natomic_write_text_file(&path, content)?;","handlingStrategy":"validation","validationCode":"fn has_parent(path: &Path) -> bool {\n    path.parent().map_or(false, |p| !p.as_os_str().is_empty())\n}\nif !has_parent(&config_path) {\n    anyhow::bail!(\"config path must include a directory: {}\", config_path.display());\n}","typeGuard":"fn is_writable_path(path: &Path) -> bool {\n    path.parent().map_or(false, |p| !p.as_os_str().is_empty())\n}","tryCatchPattern":"match update_ssh_config(...) {\n    Err(DesktopBridgeError::Io(e)) if e.kind() == ErrorKind::InvalidInput\n        && e.to_string().contains(\"parent directory\") =>\n        eprintln!(\"provide an absolute path with a directory component\"),\n    other => other,\n}","preventionTips":["Always build ssh config paths with PathBuf::join on a base directory.","Validate any env var overriding the config location is non-empty and absolute.","Never pass bare filenames to atomic-write helpers."],"tags":["filesystem","path","ssh-config"],"backgroundTag":"invalid-file-path","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}