astrid-runtime/astrid · error

shutdown stage gateway.authentication: live gateway has no…

Error message

shutdown stage gateway.authentication: live gateway has no readiness authority

What it means

Thrown by clean_unowned_gateway_startup when the gateway socket connects successfully (ConnectOutcome::Connected) during cleanup of an unowned startup. A live gateway means there is no readiness authority for this cleanup to proceed, since the process is not owned by this lease.

Solutions

  1. Stop the live gateway via the client that owns it (its ready record / PID) before cleanup
  2. Verify the socket path: ensure AST/RID gateway socket env/config points to your instance
  3. If the live gateway is legitimately yours, acquire the proper lifecycle lease instead of the unowned-cleanup path
  4. Kill the orphaned process and remove stale socket/lease files

Example fix

// before
clean_unowned_gateway_startup(...).await?;
// after: check for a live gateway first
if connect_outcome(&gateway_socket_path()?).await
    == ConnectOutcome::Connected(_) {
    // route through normal stop_ready_gateway for the live record
    stop_ready_gateway(...).await?;
} else {
    clean_unowned_gateway_startup(...).await?;
}
Defensive patterns

Strategy: fallback

Validate before calling

match astrid_core::local_transport::connect_outcome(&gateway_socket_path()?).await? {
    ConnectOutcome::Connected(_) => anyhow::bail!("live gateway present; use owned stop path"),
    _ => {},
}

Type guard

fn gateway_is_live(outcome: &ConnectOutcome) -> bool {
    matches!(outcome, ConnectOutcome::Connected(_))
}

Try / catch

match clean_unowned_gateway_startup(...).await {
    Err(e) if e.to_string().contains("no readiness authority") => {
        stop_ready_gateway(/* live record */).await?;
    },
    other => other?,
}

Prevention

When it happens

Trigger: wait_for_gateway or stop_gateway calls clean_unowned_gateway_startup while another live gateway is listening on the socket — e.g. a gateway started by a different generation/user is still serving.

Common situations: A gateway from another terminal session or IDE instance still running; stale lease cleared while the gateway process survived; two clients sharing one socket path with different lifecycles.

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/d2ff29813b0b5237. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-cli/src/commands/mcp/lifecycle.rs:636

        anyhow::bail!(
            "shutdown stage gateway.startup_reap: starting gateway PID {gateway_pid} is {outcome:?}"
        );
    }
    Ok(())
}

async fn clean_unowned_gateway_startup(lifecycle: &GatewayLifecycleLock) -> Result<()> {
    let socket = gateway_socket_path()?;
    match astrid_core::local_transport::connect_outcome(&socket)
        .await
        .context("shutdown stage gateway.listener_probe")?
    {
        astrid_core::local_transport::ConnectOutcome::Absent => {},
        astrid_core::local_transport::ConnectOutcome::Stale => {
            astrid_core::local_transport::remove_stale_endpoint(&socket)
                .context("shutdown stage gateway.stale_listener_cleanup")?;
        },
        astrid_core::local_transport::ConnectOutcome::Connected(_) => anyhow::bail!(
            "shutdown stage gateway.authentication: live gateway has no readiness authority"
        ),
    }
    remove_gateway_startup_lease(None).context("shutdown stage gateway.startup_cleanup")?;
    let _ = lifecycle.file();
    Ok(())
}

async fn request_gateway_control(
    stream: UnixStream,
    record: &GatewayReady,
    operation: GatewayControlOperation,
) -> Result<GatewayControlAck> {
    let request = GatewayControlRequest {
        version: GATEWAY_CONTROL_VERSION,
        operation,
        pid: record.pid,
        hook_token: record.hook_token.clone(),

View on GitHub (pinned to affd8760f4)