{"record":{"id":"bb253f5cb0d8fb8a","repo":"herdrdev/herdr","slug":"failed-to-create-private-herdr-ssh-config-director-bb253f","errorCode":null,"errorMessage":"failed to create private herdr ssh config directory","messagePattern":"failed to create private herdr ssh config directory","errorType":"error_code","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"src/platform/windows.rs","lineNumber":135,"sourceCode":"        system_config: std::env::var_os(\"PROGRAMDATA\")\n            .map(PathBuf::from)\n            .map(|dir| dir.join(\"ssh\").join(\"ssh_config\")),\n        multiplexing: false,\n    }\n}\n\npub(crate) fn create_remote_ssh_config_dir(_control_socket_name: &str) -> std::io::Result<PathBuf> {\n    let base = remote_private_temp_base();\n    std::fs::create_dir_all(&base)?;\n    for attempt in 0..100 {\n        let dir = base.join(format!(\"ssh-{}-{attempt}\", std::process::id()));\n        match create_remote_private_dir(&dir) {\n            Ok(()) => return Ok(dir),\n            Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => continue,\n            Err(err) => return Err(err),\n        }\n    }\n    Err(std::io::Error::new(\n        std::io::ErrorKind::AlreadyExists,\n        \"failed to create private herdr ssh config directory\",\n    ))\n}\n\npub(crate) fn create_remote_ssh_config_file(\n    path: &std::path::Path,\n) -> std::io::Result<std::fs::File> {\n    std::fs::OpenOptions::new()\n        .write(true)\n        .create_new(true)\n        .open(path)\n}\n\npub(crate) fn create_remote_private_dir(path: &std::path::Path) -> std::io::Result<()> {\n    use interprocess::os::windows::security_descriptor::{\n        AsSecurityDescriptorExt as _, SecurityDescriptor,\n    };","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/herdrdev/herdr/blob/f457cff4f2648eee85d176f8a41861241d4e8428/src/platform/windows.rs#L117-L153","documentation":"Windows implementation of create_remote_ssh_config_dir mirrors the fallback logic: it tries candidate private directories, skips ones that already exist, and if every candidate fails for a real reason (permissions, read-only disk) it returns this AlreadyExists error as a terminal 'no usable directory' marker rather than the underlying IO error.","triggerScenarios":"Calling create_remote_ssh_config_dir on Windows when create_remote_private_dir fails on every candidate directory with a non-AlreadyExists error — e.g. ACCESS_DENIED on the parent folder, read-only volume, or antivirus blocking directory creation.","commonSituations":"UserProfile redirected to a locked-down or roaming profile directory; corporate policy or AV intercepting creation of dot-directories; running Herdr from a service account whose profile path is unwritable.","solutions":["Check ACLs on %USERPROFILE% and any herdr data directories; grant the process create-directory rights","Ensure %USERPROFILE% (or the configured home) is on a writable volume","Pre-create the expected herdr ssh config directory manually so creation is unnecessary","If AV/group policy is blocking, add an exclusion for the Herdr data directory"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"let base = std::env::var_os(\"USERPROFILE\").map(std::path::PathBuf::from);\nif let Some(base) = base {\n    let meta = std::fs::metadata(&base);\n    if meta.is_err() || meta?.permissions().readonly() {\n        // surface unwritable-profile problem before calling\n    }\n}","typeGuard":null,"tryCatchPattern":"Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists && e.to_string().contains(\"private herdr ssh config\") => {\n    // no usable dir: offer fallback to %TEMP%-based dir or prompt for elevation\n}","preventionTips":["Verify the user profile directory is writable during install","Pre-create the herdr ssh config directory on first run","Add AV exclusions for the Herdr data directory in managed environments"],"tags":["ssh","windows","filesystem","permissions","directory-creation"],"backgroundTag":"directory-creation-permission-denied","analyzedSha":"f457cff4f2648eee85d176f8a41861241d4e8428","analyzedAt":"2026-08-28T15:41:09.197Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}