astrid-runtime/astrid · error

shutdown stage gateway.startup_identity: lease has no execut

Error message

shutdown stage gateway.startup_identity: lease has no executable

What it means

Thrown by stop_startup_gateway when the lease has no gateway_exe. The code needs the executable path alongside the PID to safely terminate the exact process it started (PID+exe identity check) and refuses to kill without it.

Source

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

        record.pid,
        crate::commands::daemon_control::GRACE,
    )
    .await
    {
        anyhow::bail!(
            "shutdown stage gateway.process_reap: authenticated gateway PID {} did not exit",
            record.pid
        );
    }
    remove_dead_gateway_markers(&record).await
}

async fn stop_startup_gateway(lease: &GatewayStartupLease) -> Result<()> {
    let Some(gateway_pid) = lease.gateway_pid else {
        anyhow::bail!("shutdown stage gateway.startup_identity: lease has no gateway PID");
    };
    let Some(gateway_exe) = lease.gateway_exe.as_deref() else {
        anyhow::bail!("shutdown stage gateway.startup_identity: lease has no executable");
    };
    let current = read_gateway_startup_lease()?.ok_or_else(|| {
        anyhow::anyhow!("shutdown stage gateway.startup_identity: lease disappeared")
    })?;
    if current != *lease {
        anyhow::bail!("shutdown stage gateway.startup_identity: startup generation changed");
    }

    let outcome =
        crate::commands::daemon_control::terminate_known(gateway_pid, Some(gateway_exe)).await;
    if !matches!(
        outcome,
        crate::commands::daemon_control::KillOutcome::TermExited
            | crate::commands::daemon_control::KillOutcome::KilledExited
    ) {
        anyhow::bail!(
            "shutdown stage gateway.startup_reap: starting gateway PID {gateway_pid} is {outcome:?}"
        );

View on GitHub (pinned to affd8760f4)

Solutions

  1. Remove the incomplete lease file and let the next gateway startup write a complete one
  2. Restart the gateway cleanly (start then stop) so the lease records both PID and executable
  3. Verify no mixed-version state: clear state from older astrid installations
  4. Report a bug if startup consistently produces leases without gateway_exe

Example fix

// before
let gateway_exe = lease.gateway_exe.as_deref().unwrap();
// after: ensure a full lease exists before shutdown
if lease.gateway_exe.is_none() {
    remove_gateway_startup_lease(None)?;
    anyhow::bail!("stale lease; restart gateway to regenerate");
}
Defensive patterns

Strategy: validation

Validate before calling

let lease = read_gateway_startup_lease()?;
anyhow::ensure!(
    lease.as_ref().and_then(|l| l.gateway_exe.as_deref()).is_some(),
    "startup lease missing executable; restart gateway"
);

Type guard

fn has_gateway_exe(l: &GatewayStartupLease) -> bool {
    l.gateway_exe.as_deref().is_some_and(|p| !p.is_empty())
}

Try / catch

match stop_gateway().await {
    Err(e) if e.to_string().contains("lease has no executable") => {
        remove_gateway_startup_lease(None)?; // regenerate via next startup
    },
    other => other?,
}

Prevention

When it happens

Trigger: stop_gateway called with a GatewayStartupLease whose gateway_exe is None — corrupt/truncated lease file, older lease format, or a lease deserialized from incomplete state.

Common situations: Binary was moved/renamed between start and stop so lease regeneration skipped the exe; state directory written by an older version; manual tampering with lease contents.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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