astrid-runtime/astrid · error
another MCP gateway startup or lifecycle is already active
Error message
another MCP gateway startup or lifecycle is already active
What it means
`mcp gateway run` acquires an exclusive advisory lifecycle lock (`try_acquire_gateway_lifecycle`) before starting the durable gateway. If the lock is already held, this error is thrown to prevent two gateway processes (or a startup and a shutdown) from owning the same socket, readiness file, and state directory simultaneously.
Solutions
- Check if a gateway is already running (look for the socket / `mcp-gateway.ready` file or the process holding the lifecycle lock) and use it instead of starting a new one.
- Stop the existing gateway first (`stop_gateway`) and retry the run.
- Find and terminate a hung gateway process (`ps aux | grep mcp-gateway`), then verify the lock is released and retry.
- Ensure only one supervisor (daemon, systemd unit, or script) is responsible for starting the gateway.
Example fix
// before astrid mcp gateway run # fails if already running // after astrid mcp gateway stop || true astrid mcp gateway run
Defensive patterns
Strategy: fallback
Validate before calling
// Check an existing gateway before starting
let already = std::path::Path::new(&socket_path).exists();
if already { eprintln!("gateway already running"); return Ok(()); } Try / catch
match try_acquire_gateway_lifecycle()? {
Some(lifecycle) => start_gateway(lifecycle).await,
None => attach_to_existing_gateway().await, // fall back to attach
} Prevention
- Use a single supervisor (systemd/daemon) to own gateway startup
- Attach to a running gateway instead of starting a second one
- Stop the gateway before launching a new instance
- Check for the socket/ready file in scripts before calling run
When it happens
Trigger: Running `astrid mcp gateway run` while another gateway instance already holds the lifecycle lock file (mcp-gateway.lifecycle.lock); a stale lock left by a crashed gateway whose OS-level advisory lock was not released or is held by a lingering supervisor; invoking run and stop/start concurrently.
Common situations: Launching the gateway twice in two terminals; a systemd/daemon supervisor auto-restarting the gateway while a manual instance runs; a hung prior gateway process still alive holding the lock; running the command from scripts that race at boot.
Related errors
- expected MCP attach registration
- MCP attach registration has an empty host
- MCP attach registration is missing or too large
- MCP attach replaced or idle-evicted
- MCP gateway attach limit reached for principal
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/7068afce80d09819.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/mcp/gateway.rs:442
impl AttachReservation {
async fn install(
self,
state: &GatewayState,
host_session_id: String,
slot: AttachSlot,
) -> OwnedSemaphorePermit {
let Self { admission, permit } = self;
state.install_slot(host_session_id, slot).await;
drop(admission);
permit
}
}
/// Run the durable MCP gateway until it is terminated.
pub(crate) async fn run(principal: Option<&str>) -> Result<ExitCode> {
let caller = super::lifecycle::resolve_principal(principal)?;
let lifecycle = try_acquire_gateway_lifecycle()?.ok_or_else(|| {
anyhow::anyhow!("another MCP gateway startup or lifecycle is already active")
})?;
let daemon_root = std::env::current_dir().context("failed to read MCP gateway cwd")?;
let boot_token = mint_boot_token();
let gateway_exe = std::env::current_exe()
.and_then(std::fs::canonicalize)
.context("failed to resolve MCP gateway executable identity")?;
let startup_lease = GatewayStartupLease {
version: 1,
principal: caller.to_string(),
boot_token: boot_token.clone(),
supervisor_pid: std::process::id(),
gateway_pid: Some(std::process::id()),
gateway_exe: Some(gateway_exe),
};
write_gateway_startup_lease(&startup_lease)?;
let mut lease_guard = StartupLeaseGuard {
boot_token: boot_token.clone(),View on GitHub (pinned to affd8760f4)