astrid-runtime/astrid · error

MCP attach registration hook_token is invalid

Error message

MCP attach registration hook_token is invalid

What it means

authenticate_registration compares the hook_token in the attach registration against the token stored in GatewayState. A mismatch means the caller does not share the gateway's secret, so the request is rejected even if the principal matches. This complements the principal check as a bearer-token authentication layer.

Solutions

  1. Re-read the current gateway readiness record and use its hook_token verbatim (no trimming/transforming)
  2. Confirm you're connecting to the correct gateway socket for your session
  3. Restart the gateway and use the freshly minted token
  4. Check the token wasn't truncated or re-encoded during serialization

Example fix

// before
hook_token: old_cached_token,
// after
let ready = read_gateway_ready()?.expect("gateway ready record");
registration.hook_token = ready.hook_token;
Defensive patterns

Strategy: validation

Validate before calling

if reg.hook_token != state.hook_token { return Err("stale hook_token — re-read readiness record"); }

Try / catch

if let Err(e) = authenticate_registration(&reg, &state) {
    if e.to_string().contains("hook_token is invalid") { refresh_token_and_retry(); }
}

Prevention

When it happens

Trigger: serve_attach (or tests forged_principal_cannot_select_another_gateway_uplink / missing_hook_token_is_rejected_before_uplink_selection) supplies a hook_token that differs from state.hook_token — e.g. a token from a previous gateway run, another gateway, or a fabricated value.

Common situations: Stale readiness metadata from a previous gateway generation read by the client; two gateways running concurrently and the client mixed up tokens; token rotated on the gateway side but the client cached the old one.

Understand the failure class

Related errors


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

Appendix: source

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

    }
    validate_workspace(&registration.workspace_abs)?;
    Ok(())
}

fn authenticate_registration(
    registration: &AttachRegistration,
    state: &GatewayState,
) -> Result<astrid_core::PrincipalId> {
    let principal = super::lifecycle::resolve_principal(Some(&registration.principal))?;
    if principal != state.principal {
        anyhow::bail!(
            "MCP attach registration principal '{}' is not the authenticated gateway principal '{}'",
            principal,
            state.principal
        );
    }
    if registration.hook_token != state.hook_token {
        anyhow::bail!("MCP attach registration hook_token is invalid");
    }
    Ok(principal)
}

fn mint_hook_token() -> String {
    format!(
        "{}{}",
        Uuid::new_v4().as_simple(),
        Uuid::new_v4().as_simple()
    )
}

fn mint_boot_token() -> String {
    Uuid::new_v4().as_simple().to_string()
}

fn validate_workspace(value: &str) -> Result<PathBuf> {
    if value.is_empty() {

View on GitHub (pinned to affd8760f4)