astrid-runtime/astrid · error
Daemon closed the connection before responding
Error message
Daemon closed the connection before responding
What it means
While waiting for a response frame in send_and_wait, a None from read_raw_frame means the daemon closed its end of the connection without sending the awaited response. The library surfaces this as a distinct error so callers do not mistake a dead connection for a timeout and can immediately reconnect or report daemon death.
Source
Thrown at crates/astrid-uplink/src/admin_client.rs:190
.with_principal(self.caller.to_string());
self.inner.send_message(msg).await?;
let deadline = tokio::time::Instant::now()
.checked_add(self.timeout)
.unwrap_or_else(tokio::time::Instant::now);
loop {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
anyhow::bail!(
"Admin request timed out after {:?} waiting for {want_response}",
self.timeout
);
}
let read = tokio::time::timeout(remaining, self.inner.read_raw_frame()).await;
let frame = match read {
Ok(Ok(Some(bytes))) => bytes,
Ok(Ok(None)) => {
anyhow::bail!("Daemon closed the connection before responding");
},
Ok(Err(e)) => return Err(e),
Err(_) => {
anyhow::bail!(
"Admin request timed out after {:?} waiting for {want_response}",
self.timeout
);
},
};
// The host serializes IPC envelopes through `to_guest_bytes`
// which strips the `type` tag for `IpcPayload::RawJson`, so
// the bytes the uplink sees from the proxy embed the
// response directly under `payload` (no `IpcPayload`
// wrapper). Match by topic, then deserialize
// `AdminKernelResponse` straight out of the `payload` field.
let raw: Value = match serde_json::from_slice(&frame) {
Ok(v) => v,View on GitHub (pinned to affd8760f4)
Solutions
- Inspect the daemon's logs at the time of the call to find the crash/panic and fix that root cause.
- Reconnect and retry the request once a fresh daemon instance is running.
- If the daemon is supervisor-managed, verify restart policy and resource limits (e.g. OOM) and adjust.
- Check that the daemon binary was not killed by a deploy/stop script racing with in-flight admin calls.
Example fix
// before: no reconnection on closed connection
let reply = admin_client.request(req).await?;
// after: recreate the client (and connection) on closed-connection errors
let reply = match admin_client.request(req).await {
Ok(r) => r,
Err(e) if e.to_string().contains("closed the connection") => {
admin_client = AdminClient::connect(&socket_path).await?;
admin_client.request(req).await?
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: try-catch
Validate before calling
// Confirm the daemon is alive before sending
if !daemon_process_running() || !socket_path_exists() {
restart_daemon()?;
} Try / catch
match admin_client.request(req).await {
Ok(resp) => resp,
Err(e) if e.to_string().contains("closed the connection") => {
// daemon died mid-request: reconnect to a fresh instance and retry
admin_client = AdminClient::connect(&socket_path).await?;
admin_client.request(req).await?
}
Err(e) => return Err(e),
} Prevention
- Run the daemon under a supervisor with automatic restart.
- Check daemon logs/OOM events after every closed-connection error.
- Avoid stopping/redeploying the daemon while admin requests are in flight.
- Monitor daemon memory so OOM kills are caught before clients see disconnects.
When it happens
Trigger: Calling AdminClient::request or request_agent_derive when the daemon process exits or drops the admin connection mid-request — crash, panic, OOM kill, or deliberate shutdown — before writing the response frame.
Common situations: Daemon crashed while handling this request (check daemon logs/core dump); daemon was restarted or stopped by a supervisor during the call; OS OOM-killer terminated the daemon; connection accepted then closed because the daemon hit a fatal internal error.
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
- daemon connection closed before command result
- daemon closed guard uplink
- Admin request timed out after {:?} waiting for {want_respons
- daemon metadata lookup failed: {error}
- unexpected daemon metadata response: {other:?}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/151bb4642ad969cc.
Report an issue: GitHub.