nikivdev/code · error · anyhow::Error

ssh-agent failed

Error message

ssh-agent failed: {}

What it means

ensure_flow_agent spawns `ssh-agent -a <sock> -s` and checks its exit status; if the process exits nonzero the captured stderr is wrapped in this message and returned. It means the local ssh-agent could not be started, so no agent socket is available for SSH operations.

Solutions

  1. Remove any stale socket file at the target path and retry
  2. Shorten the socket path (shorter temp dir) if it exceeds ~104-108 characters
  3. Ensure openssh-client (ssh-agent) is installed and on PATH
  4. Check permissions on the directory holding the socket

Example fix

// before: stale socket causes agent bind failure
.output()
.context("failed to start ssh-agent")?;
// after: clear stale socket first
if sock.exists() { let _ = std::fs::remove_file(&sock); }
let output = Command::new("ssh-agent")
    .args(["-a", sock.to_string_lossy().as_ref(), "-s"])
    .output()
    .context("failed to start ssh-agent")?;
Defensive patterns

Strategy: fallback

Validate before calling

let sock = agent_socket_path();
if sock.as_os_str().len() >= 108 {
    bail!("socket path too long for unix sockets: {}", sock.display());
}
if sock.exists() {
    let _ = std::fs::remove_file(&sock); // clear stale socket
}
if which::which("ssh-agent").is_err() {
    bail!("ssh-agent not installed; install openssh-client");
}

Try / catch

match ensure_flow_agent() {
    Ok(agent) => use_agent(agent),
    Err(e) if e.to_string().contains("ssh-agent failed") => {
        eprintln!("agent start failed: {e:#}; falling back to existing SSH_AUTH_SOCK");
        std::env::var("SSH_AUTH_SOCK").map(Agent::Existing)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: ssh-agent binary missing or failing to bind the requested socket path; socket path too long (>108 chars on Unix domain sockets); stale socket file already exists at the path; permission denied on the socket directory.

Common situations: Long TMPDIR paths exceeding Unix socket path limits; leftover socket from a crashed previous run; read-only or noexec temp directory; minimal container images without openssh-client installed.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/8489f07314ae9987. Report an issue: GitHub.

Appendix: source

Thrown at src/ssh.rs:216

    if sock.exists() {
        if probe_agent(&sock) {
            return Ok(sock);
        }
        let _ = fs::remove_file(&sock);
    }
    let state_path = flow_agent_state_path();
    if let Some(parent) = state_path.parent() {
        fs::create_dir_all(parent)?;
    }

    let output = Command::new("ssh-agent")
        .args(["-a", sock.to_string_lossy().as_ref(), "-s"])
        .output()
        .context("failed to start ssh-agent")?;

    if !output.status.success() {
        let stderr = String::from_utf8_lossy(&output.stderr);
        bail!("ssh-agent failed: {}", stderr.trim());
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    let pid = parse_agent_output(&stdout, "SSH_AGENT_PID")
        .and_then(|val| val.parse::<u32>().ok())
        .context("failed to parse ssh-agent pid")?;
    let sock_path = parse_agent_output(&stdout, "SSH_AUTH_SOCK")
        .map(PathBuf::from)
        .unwrap_or_else(|| sock.clone());

    let state = FlowAgentState {
        pid,
        sock: sock_path.clone(),
    };
    let content = serde_json::to_string_pretty(&state)?;
    fs::write(&state_path, content)?;

    Ok(sock_path)

View on GitHub (pinned to a747e741ae)