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
- Stop the live gateway via the client that owns it (its ready record / PID) before cleanup
- Verify the socket path: ensure AST/RID gateway socket env/config points to your instance
- If the live gateway is legitimately yours, acquire the proper lifecycle lease instead of the unowned-cleanup path
- 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
- Ensure only one gateway instance uses a given socket path
- Stop gateways via the client that owns them
- Check for other sessions/IDE instances before forced cleanup
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.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- FSKit service control endpoint is already present
- MCP gateway socket path has no parent
- shutdown stage daemon.listener_absence: endpoint remains…
- an Astrid daemon appears to be running but its uplink is…
- an Astrid daemon appears to be running but its uplink is…
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)