astrid-runtime/astrid · error
failed to connect as '{principal}': {e}
Error message
failed to connect as '{principal}': {e} What it means
Raised in run_job_under when socket_client::connect_for_workspace fails to establish a connection to the daemon socket as the given principal. The underlying connection error (socket missing, permission denied, handshake rejected) is wrapped with the principal name for context.
Source
Thrown at crates/astrid-cli/src/commands/agent/spawn.rs:162
eprintln!("[spawn] job failed: {job:#}");
eprintln!("[spawn] teardown also failed: {teardown:#}");
Ok(ExitCode::from(1))
},
}
}
/// Connect an uplink authenticated AS the throwaway, submit the job, and drain
/// the response under a wall-clock ceiling. On timeout, send the cooperative
/// cancel sentinel; the hard guarantee is the caller's teardown regardless.
async fn run_job_under(
principal: &PrincipalId,
session: &SessionId,
job: &str,
timeout_secs: u64,
) -> Result<String> {
let mut client = socket_client::connect_for_workspace(session.clone(), principal.clone(), None)
.await
.map_err(|e| anyhow!("failed to connect as '{principal}': {e}"))?;
client
.send_input(job.to_string())
.await
.context("failed to submit job")?;
let drained = tokio::time::timeout(
Duration::from_secs(timeout_secs),
drain_until_final(&mut client, session),
)
.await;
let result = match drained {
Ok(inner) => inner,
Err(_elapsed) => {
// Cooperative cancel so the react capsule aborts the in-flight turn
// promptly; delete (which reclaims) is the hard stop regardless.
let _ = send_cancel(&mut client, session).await;View on GitHub (pinned to affd8760f4)
Solutions
- Start/restart the daemon and confirm the socket file exists
- Verify the principal is authorized and has filesystem permission on the socket
- Check the daemon logs for the root connect error in the {e} suffix
Example fix
// before astrid-agent spawn --job run-tests # daemon not running // after astrid-agent daemon start && astrid-agent spawn --job run-tests
Defensive patterns
Strategy: retry
Validate before calling
if !std::path::Path::new(&socket_path).exists() { eprintln!("daemon socket not found; start the daemon first"); std::process::exit(1); } Try / catch
match run_job_under(...).await { Err(e) if e.to_string().contains("failed to connect") => { start_daemon()?; retry_once(); }, Err(e) => return Err(e), Ok(r) => Ok(r) } Prevention
- Ensure the daemon is running before spawning jobs
- Check socket file permissions match the invoking user
- Health-check the daemon (ping/status) before submitting jobs
When it happens
Trigger: Daemon not running or its socket file absent, socket path wrong for the workspace, the principal lacks permission to open the socket, or the daemon crashed mid-startup.
Common situations: Running the spawn command before starting the daemon, running as a different user than the daemon owner so the Unix socket is inaccessible, stale socket file after an unclean daemon shutdown.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
- Failed to connect to daemon: {e}
- capsules are installed, but connecting to the selected works
- connection timed out after 5s
- daemon request: {e}
- Failed to connect to daemon. Check logs: {}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/de89b3765cf1086b.
Report an issue: GitHub.