astrid-runtime/astrid · error

MCP gateway is not ready for principal

Error message

MCP gateway is not ready for principal '{caller}'; run `aos mcp ready --format hook`

What it means

The mcp attach command requires a gateway readiness file (read via read_gateway_ready) that records which principal performed `aos mcp ready`. If the file is absent, attach cannot trust that the gateway handshake completed, so it fails with instructions to run the ready hook. The check is deliberately keyed to the authenticated caller, never to a registration-supplied principal.

Solutions

  1. Run `aos mcp ready --format hook` as the same authenticated principal, then retry attach
  2. Confirm the readiness file exists at the gateway socket path (check gateway_socket_path()) and that TMPDIR/runtime dir matches between ready and attach
  3. Re-run the full MCP setup sequence (ready then attach) in the same session/user context

Example fix

# before
$ aos mcp attach            # fails: no readiness file
# after
$ aos mcp ready --format hook
$ aos mcp attach
Defensive patterns

Strategy: try-catch

Validate before calling

// before attaching, check the readiness file exists and matches the caller
if read_gateway_ready()?.map(|r| r.principal) != Some(crate::principal::current().to_string()) {
    eprintln!("Run `aos mcp ready --format hook` as the active principal first");
}

Try / catch

match mcp::attach::run(principal, workspace).await {
    Err(e) if e.to_string().contains("MCP gateway is not ready") => {
        eprintln!("Run `aos mcp ready --format hook`, then retry attach");
        std::process::exit(1);
    }
    r => r?,
}

Prevention

When it happens

Trigger: Running `aos mcp attach` when: the readiness file was never created (ready hook not run), it was deleted by cleanup/temp reaping, it expired, or it exists for a different TMPDIR/XDG runtime dir than the one this process sees.

Common situations: Skipping the `aos mcp ready --format hook` step in a new shell/session; running attach from a cron/CI context where the interactive ready hook never ran; stale socket dir after reboot.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at crates/astrid-cli/src/commands/mcp/attach.rs:31

use tokio::io::{AsyncWriteExt, copy};
use tokio::net::UnixStream;

use super::lifecycle::{
    ATTACH_REGISTRATION_VERSION, AttachRegistration, gateway_socket_path, read_gateway_ready,
};

/// Attach this process's stdio to the principal's persistent MCP gateway.
///
/// `workspace` is host project context, not an Astrid home or daemon root. It
/// is sent in a small registration preface so the gateway can preserve the
/// caller's `cwd://` root while sharing one daemon uplink across windows.
pub(crate) async fn run(_principal: Option<&str>, workspace: Option<&Path>) -> Result<ExitCode> {
    // The process-wide principal was authenticated before dispatch. Never
    // treat a registration field as the source of authority for this attach.
    let caller = crate::principal::current();
    let socket = gateway_socket_path()?;
    let ready = read_gateway_ready()?.ok_or_else(|| {
        anyhow::anyhow!(
            "MCP gateway is not ready for principal '{caller}'; run `aos mcp ready --format hook`"
        )
    })?;
    if ready.principal != caller.to_string() {
        anyhow::bail!(
            "MCP gateway is ready for principal '{}', not '{}'; run `aos mcp ready --format hook` for the active principal",
            ready.principal,
            caller
        );
    }
    let stream = UnixStream::connect(&socket).await.with_context(|| {
        format!(
            "failed to connect to MCP gateway at {}; run `aos mcp ready --format hook`",
            socket.display()
        )
    })?;

    let registration = build_registration(&caller, workspace, &ready)?;

View on GitHub (pinned to affd8760f4)