astrid-runtime/astrid · error

Daemon rejected connection

Error message

Daemon rejected connection: {reason}

What it means

perform_handshake_in_home performs the client handshake against the daemon's socket in the home directory and expects an OK response carrying optional auth state. If the daemon replies with a non-ok status, the library bails with the daemon-supplied reason (or 'unknown error' when absent), so the caller learns why authentication/handshake was refused.

Solutions

  1. Read the reason string in the error and fix the stated cause (e.g. re-authenticate or regenerate client credentials).
  2. Re-run the login/enrollment flow so the client's home-directory credentials match what the daemon expects.
  3. Confirm the daemon version and handshake protocol match the client crate; upgrade whichever is stale.
  4. Check daemon-side limits (max connections, auth mode) that could make it reject new handshakes.

Example fix

// before: stale credentials in home cause rejection
let client = SocketClient::connect_default().await?; // Daemon rejected connection: invalid token
// after: refresh credentials before connecting
auth::login(&config).await?; // refreshes the home credential file
let client = SocketClient::connect_default().await?;
Defensive patterns

Strategy: validation

Validate before calling

// Verify credentials exist and look well-formed before attempting a handshake
fn creds_look_valid(home: &Path) -> bool {
    let cred = home.join(".astrid/credentials");
    cred.exists() && std::fs::read(&cred).map(|b| !b.is_empty()).unwrap_or(false)
}
if !creds_look_valid(&dirs::home_dir().unwrap()) {
    auth::login(&config).await?; // refresh before connecting
}

Try / catch

match perform_handshake(&client).await {
    Ok(auth) => auth,
    Err(e) if e.to_string().contains("Daemon rejected connection") => {
        let reason = e.to_string();
        if reason.contains("token") || reason.contains("auth") {
            auth::login(&config).await?; // re-authenticate and retry once
            perform_handshake(&client).await?
        } else { return Err(e); }
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling perform_handshake (directly or via clients like the test helpers) when the daemon answers the handshake request with an error status — bad or missing credentials/token, handshake protocol version mismatch, daemon not accepting new connections, or device/session not registered.

Common situations: Client credentials are stale or rotated on the daemon side; daemon requires re-authentication after key rotation; a different user's home directory lacks the expected credentials file; daemon policy (capacity/auth mode) changed between versions.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/e68226b10b16bc5d. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-uplink/src/socket_client.rs:569

        );
        let signature = keypair.sign(message.as_bytes()).to_hex();
        let signed = HandshakeRequest {
            token: token.to_hex(),
            protocol_version: PROTOCOL_VERSION,
            client_version: env!("CARGO_PKG_VERSION").to_string(),
            claimed_principal: Some(principal.to_string()),
            signature: Some(signature),
        };
        (send_request_read_response(stream, &signed).await?, true)
    } else {
        (response, false)
    };

    if !response.is_ok() {
        let reason = response
            .reason
            .unwrap_or_else(|| "unknown error".to_string());
        anyhow::bail!("Daemon rejected connection: {reason}");
    }

    Ok(authenticated)
}

/// Test-only access to the production handshake framing with an explicit home.
#[cfg(feature = "test-support")]
#[doc(hidden)]
pub async fn perform_handshake_for_test(
    stream: &mut LocalStream,
    principal: &PrincipalId,
    home: &astrid_core::dirs::AstridHome,
) -> Result<bool> {
    perform_handshake_in_home(stream, principal, home).await
}

/// Write one length-prefixed [`HandshakeRequest`] frame and read the
/// length-prefixed [`HandshakeResponse`] frame, with per-operation timeouts.

View on GitHub (pinned to affd8760f4)