astrid-runtime/astrid · warning

MCP gateway startup generation changed before cleanup

Error message

MCP gateway startup generation changed before cleanup

What it means

remove_gateway_startup_lease deletes the startup lease but only if it still belongs to the current boot generation, identified by the boot_token recorded in the lease. If another gateway instance has already replaced the lease (different boot_token), removing it would clobber the successor, so this error aborts the cleanup. It's a generation-check that makes cleanup safe under concurrent gateway restarts.

Source

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

    let temp = path.with_extension(format!("starting.tmp.{}", std::process::id()));
    let bytes = serde_json::to_vec(lease).context("failed to encode MCP gateway startup lease")?;
    std::fs::write(&temp, bytes).with_context(|| format!("failed to write {}", temp.display()))?;
    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        std::fs::set_permissions(&temp, std::fs::Permissions::from_mode(0o600))?;
    }
    std::fs::rename(&temp, &path).with_context(|| format!("failed to publish {}", path.display()))
}

pub(crate) fn remove_gateway_startup_lease(boot_token: Option<&str>) -> Result<()> {
    let path = gateway_startup_lease_path()?;
    let lease = read_gateway_startup_lease()?;
    if let Some(lease) = lease
        && let Some(expected) = boot_token
        && lease.boot_token != expected
    {
        anyhow::bail!("MCP gateway startup generation changed before cleanup");
    }
    match std::fs::remove_file(&path) {
        Ok(()) => Ok(()),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
        Err(error) => Err(error).with_context(|| format!("failed to remove {}", path.display())),
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum GatewayControlOperation {
    Health,
    Stop,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct GatewayControlRequest {
    pub version: u8,

View on GitHub (pinned to affd8760f4)

Solutions

  1. Skip cleanup — the new generation owns the lease; nothing to do
  2. Ensure gateway startup/shutdown operations are serialized (lock or supervisor) so generations don't overlap
  3. Verify you pass the correct current boot_token from your own startup flow
  4. If the successor is unwanted, stop it and then remove the lease with its own boot_token

Example fix

// before
remove_gateway_startup_lease(Some(&stale_boot_token))?;
// after
if let Ok(Some(lease)) = read_gateway_startup_lease() {
    if lease.boot_token == my_boot_token { remove_gateway_startup_lease(Some(&my_boot_token))?; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if let Some(lease) = read_gateway_startup_lease()? {
    if let Some(expected) = &my_boot_token {
        if lease.boot_token != *expected { /* successor owns it — skip cleanup */ }
    }
}

Try / catch

match remove_gateway_startup_lease(Some(&boot_token)) {
    Ok(()) => {},
    Err(e) if e.to_string().contains("generation changed") => {}, // successor owns the lease; safe to ignore
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: drop/shutdown_gateway/clean_unowned_gateway_startup/remove_dead_gateway_markers call remove_gateway_startup_lease with an expected boot_token while the lease file on disk was rewritten by a newer gateway with a different boot_token.

Common situations: Two gateway startups racing (one finished while another was shutting down); a test or supervisor restarted the gateway before the old instance's cleanup ran; stale cleanup task executing long after a new generation started.

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