herdrdev/herdr · error · io::Error

failed to create private herdr ssh config directory

Error message

failed to create private herdr ssh config directory

What it means

create_remote_ssh_config_dir walks candidate parent directories trying to create herdr's private SSH config dir with mode 0700 (src/platform/unix_common.rs:50-76). If a candidate exists but is not a usable directory the loop skips it, and when no candidate succeeds the function returns AlreadyExists with 'failed to create private herdr ssh config directory'. It is a fallback path used when the preferred per-SSH-host config location is unavailable.

Source

Thrown at src/ipc.rs:95

        Ok(listener)
    }
}

pub(crate) fn prepare_socket_path(
    path: &Path,
    busy_message: impl FnOnce(&Path) -> String,
) -> io::Result<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }

    if !path.exists() {
        return Ok(());
    }

    match connect_local_stream(path) {
        Ok(_) => {
            return Err(io::Error::new(io::ErrorKind::AddrInUse, busy_message(path)));
        }
        Err(err) if stale_socket_connect_error(err.kind()) => {}
        Err(err) => return Err(err),
    }

    if let Err(err) = fs::remove_file(path) {
        if err.kind() != io::ErrorKind::NotFound {
            return Err(err);
        }
    }

    Ok(())
}

fn stale_socket_connect_error(kind: io::ErrorKind) -> bool {
    matches!(
        kind,
        io::ErrorKind::ConnectionRefused | io::ErrorKind::NotFound | io::ErrorKind::TimedOut

View on GitHub (pinned to f457cff4f2)

Solutions

  1. Inspect the candidate paths (under ~/.ssh) and remove or rename any regular file occupying the expected herdr config dir name
  2. Fix permissions on the parent directory (chmod u+rwx ~/.ssh) or clear root-owned leftovers
  3. Unset stray SSH-related env overrides (SSH_HOME/HERDR_* socket overrides) and retry
  4. Retry after fixing $HOME or the filesystem (e.g. read-only mount)

Example fix

# before: a file blocks the private config dir
ls -la ~/.ssh/herdr*  # shows a regular file

# after
rm ~/.ssh/herdr-ssh-config  # then retry the ssh session command
Defensive patterns

Strategy: validation

Validate before calling

for candidate in candidate_parents() {
    let dir = candidate.join(HERDR_SSH_DIR_NAME);
    if dir.is_file() { /* conflict: remove or bail with a clear error */ }
}

Try / catch

match create_remote_ssh_config_dir(&path) {
    Err(e) if e.kind() == io::ErrorKind::AlreadyExists =>
        cleanup_conflicting_entries_and_retry(&path),
    r => r,
}

Prevention

When it happens

Trigger: Preparing a remote SSH control/config path on Unix where every candidate parent already contains a conflicting non-directory entry named the same as the private herdr ssh config dir, or where creation otherwise fails with an error other than success/AlreadyExists-skip.

Common situations: Stale files left at the expected directory path by older herdr versions, read-only or permission-restricted parent dirs ($HOME/.ssh locked down, root-owned leftovers), or containers with unusual HOME layouts.

Related errors


AI-assisted analysis of herdrdev/herdr@f457cff4f2 (2026-08-28). Data as JSON: /api/errors/e219b3902495f6b6. Report an issue: GitHub.