astrid-runtime/astrid · error

shutdown stage gateway.startup_identity: startup generation

Error message

shutdown stage gateway.startup_identity: startup generation changed

What it means

Thrown when the lease currently on disk differs from the in-memory lease being shut down, meaning a newer gateway startup generation has replaced this one. Stopping the old lease's process would be wrong, so the code bails.

Source

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

            "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:?}"
        );
    }
    Ok(())
}

async fn clean_unowned_gateway_startup(lifecycle: &GatewayLifecycleLock) -> Result<()> {
    let socket = gateway_socket_path()?;

View on GitHub (pinned to affd8760f4)

Solutions

  1. Re-read the current lease and only stop the gateway you actually own (retry the stop against the newest lease)
  2. Serialize startup/shutdown with a lock so only one generation exists at a time
  3. If the old gateway is harmless, skip the stop — its markers are now owned by the new generation
  4. Check for duplicate cron/systemd units or IDE extensions each starting the gateway

Example fix

// before
stop_gateway().await?;
// after: tolerate generation change
match stop_gateway().await {
    Err(e) if e.to_string().contains("startup generation changed") => {
        // newer gateway owns the lease; nothing to do
    },
    other => other?,
}
Defensive patterns

Strategy: retry

Validate before calling

let on_disk = read_gateway_startup_lease()?;
if on_disk.as_ref() != Some(&my_lease) {
    // generation already replaced; skip the stop
    return Ok(());
}

Type guard

fn lease_is_current(mine: &GatewayStartupLease) -> bool {
    read_gateway_startup_lease()
        .ok()
        .flatten()
        .map(|cur| cur == *mine)
        .unwrap_or(false)
}

Try / catch

match stop_gateway().await {
    Err(e) if e.to_string().contains("startup generation changed") => {
        // benign: a newer generation owns the lease
    },
    other => other?,
}

Prevention

When it happens

Trigger: Two overlapping gateway startups: process A holds a lease, process B starts and overwrites the lease, then A's stop_gateway runs and detects current != *lease. Also happens when stop is invoked after a concurrent re-start.

Common situations: Running two astrid MCP clients simultaneously; a watchdog/auto-restart spawned a new gateway while an old shutdown was in flight; stale script calling stop twice across generations.

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