astrid-runtime/astrid · error · GatewayError::Internal

daemon request: {e}

Error message

daemon request: {e}

What it means

issue_invite forwards the invite request to the daemon via the admin client; any transport or request failure is wrapped as an Internal error with context 'daemon request: {e}'. This indicates the gateway could not complete the round-trip to the daemon, as opposed to the daemon rejecting the invite itself (which becomes Forbidden).

Source

Thrown at crates/astrid-gateway/src/routes/invites.rs:93

        .extensions()
        .get::<CallerContext>()
        .cloned()
        .ok_or(GatewayError::Unauthorized)?;
    let bytes = axum::body::to_bytes(req.into_body(), 64 * 1024)
        .await
        .map_err(|e| GatewayError::BadRequest(format!("body read: {e}")))?;
    let body: IssueRequest = serde_json::from_slice(&bytes)?;

    let client = state.admin_client_for(&caller)?;
    let resp = client
        .request(AdminRequestKind::InviteIssue {
            group: body.group,
            expires_secs: body.expires_secs,
            max_uses: body.max_uses,
            metadata: body.metadata,
        })
        .await
        .map_err(|e| GatewayError::Internal(anyhow::anyhow!("daemon request: {e}")))?;
    match resp {
        AdminResponseBody::Invite(invite) => Ok(Json(IssueResponse { invite })),
        AdminResponseBody::Error(msg) => Err(GatewayError::Forbidden { reason: msg }),
        other => Err(GatewayError::Internal(anyhow::anyhow!(
            "unexpected response shape: {other:?}"
        ))),
    }
}

/// `OpenAPI` schema mirror of [`astrid_core::kernel_api::InviteSummary`].
/// Never constructed; resolves the `value_type` on
/// [`ListResponse::invites`] to a typed schema. Keep it
/// field-for-field with the serialized shape of `InviteSummary` —
/// note the field is `token_fingerprint` (not `fingerprint`), and
/// `issued_at_epoch` is always present.
#[derive(ToSchema)]
pub struct InviteSummaryView {
    /// Domain-separated `blake3:<hex>` fingerprint of the token. Raw tokens are never

View on GitHub (pinned to affd8760f4)

Solutions

  1. Check that the daemon process is running and its socket/address matches the gateway's admin client config
  2. Inspect the wrapped source error (`e`) in logs to distinguish connection-refused vs timeout vs permission issues
  3. Retry the invite issuance after confirming daemon health

Example fix

// before
.map_err(|e| GatewayError::Internal(anyhow::anyhow!("daemon request: {e}")))?;
// after
.map_err(|e| {
    tracing::error!(error = %e, "daemon request failed while issuing invite");
    GatewayError::Internal(anyhow::anyhow!("daemon request: {e}"))
})?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Probe daemon reachability before issuing the invite
client.request(AdminRequestKind::Ping).await
    .map_err(|_| anyhow::anyhow!("daemon unreachable"))?;

Try / catch

match issue_invite(State(state), caller, Json(body)).await {
    Ok(resp) => resp,
    Err(GatewayError::Internal(e)) if e.to_string().starts_with("daemon request") => {
        tracing::error!(%e, "daemon unreachable during invite issuance");
        service_unavailable("daemon temporarily unavailable; retry")
    }
    Err(GatewayError::Forbidden { reason }) => forbidden(reason),
    Err(e) => internal_error(e),
}

Prevention

When it happens

Trigger: The client.request(AdminRequestKind::InviteIssue) future fails — daemon socket not reachable, connection dropped, timeout, or serialization failure while sending the invite issue request.

Common situations: Daemon process not running; wrong daemon socket/address in gateway config; daemon restarting during a request; permission denied on the unix socket.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


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