astrid-runtime/astrid · error

expected MCP attach registration

Error message

expected MCP attach registration

What it means

read_registration_inner reads one line-delimited JSON gateway request from an attach client and requires it to be a GatewayRequest::Attach registration. If the client sends a Control request instead (or any non-Attach variant), the gateway rejects it because the very first preface on an attach connection must be a registration.

Source

Thrown at crates/astrid-cli/src/commands/mcp/gateway.rs:867

    result.map(|_| ())
}

async fn read_registration<R>(reader: &mut BufReader<R>) -> Result<AttachRegistration>
where
    R: AsyncRead + Unpin,
{
    timeout(REGISTRATION_TIMEOUT, read_registration_inner(reader))
        .await
        .context("timed out reading MCP attach registration")?
}

async fn read_registration_inner<R>(reader: &mut BufReader<R>) -> Result<AttachRegistration>
where
    R: AsyncRead + Unpin,
{
    match read_gateway_request_inner(reader).await? {
        GatewayRequest::Attach(registration) => Ok(registration),
        GatewayRequest::Control(_) => anyhow::bail!("expected MCP attach registration"),
    }
}

async fn read_gateway_request<R>(reader: &mut BufReader<R>) -> Result<GatewayRequest>
where
    R: AsyncRead + Unpin,
{
    timeout(REGISTRATION_TIMEOUT, read_gateway_request_inner(reader))
        .await
        .context("timed out reading MCP gateway registration")?
}

async fn read_gateway_request_inner<R>(reader: &mut BufReader<R>) -> Result<GatewayRequest>
where
    R: AsyncRead + Unpin,
{
    let mut line = Vec::new();
    let mut terminated = false;

View on GitHub (pinned to affd8760f4)

Solutions

  1. Ensure the attach client sends a JSON GatewayRequest::Attach registration as the first line after connecting to the attach socket.
  2. Send control operations (stop/status) over the gateway control channel, not the per-attach registration preface.
  3. Check the client's ATTACH_REGISTRATION_VERSION and message shape match the current protocol version.
  4. If writing a custom client, model it on the existing attach implementation: single-line JSON, correct variant, within 16 KiB and REGISTRATION_TIMEOUT.

Example fix

// before: control preface on attach socket
{"type":"control","version":1,...}
// after: attach registration preface
{"type":"attach","version":1,"host":"...","host_session_id":"...","principal":"...","hook_token":"..."}
Defensive patterns

Strategy: validation

Validate before calling

fn is_attach_preface(first_line: &[u8]) -> bool {
    serde_json::from_slice::<serde_json::Value>(first_line)
        .ok()
        .and_then(|v| v.get("type").cloned())
        .map_or(false, |t| t == "attach")
}

Type guard

fn is_attach(req: &GatewayRequest) -> bool {
    matches!(req, GatewayRequest::Attach(_))
}

Try / catch

match result {
    Err(e) if e.to_string().contains("expected MCP attach registration") => eprintln!("attach socket requires an Attach preface first"),
    other => other?,
}

Prevention

When it happens

Trigger: An attach client sends a GatewayRequest::Control message as its first preface over the Unix socket; read_registration (used at connection setup and by oversized_registration_preface_fails_without_waiting_for_a_newline) encounters the Control variant.

Common situations: A client implementation or script speaks the control protocol (stop/status) over the attach socket instead of the dedicated control path; protocol mix-ups where a reconnect sends the wrong preface; a corrupted or misrouted connection.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


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