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

  1. 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.
  2. Stop the existing gateway first (`stop_gateway`) and retry the run.
  3. Find and terminate a hung gateway process (`ps aux | grep mcp-gateway`), then verify the lock is released and retry.
  4. 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

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


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)