{"record":{"id":"b399f07129daedf0","repo":"astrid-runtime/astrid","slug":"mcp-gateway-startup-generation-changed-before-clea","errorCode":null,"errorMessage":"MCP gateway startup generation changed before cleanup","messagePattern":"MCP gateway startup generation changed before cleanup","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/astrid-cli/src/commands/mcp/lifecycle.rs","lineNumber":203,"sourceCode":"    let temp = path.with_extension(format!(\"starting.tmp.{}\", std::process::id()));\n    let bytes = serde_json::to_vec(lease).context(\"failed to encode MCP gateway startup lease\")?;\n    std::fs::write(&temp, bytes).with_context(|| format!(\"failed to write {}\", temp.display()))?;\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::PermissionsExt;\n        std::fs::set_permissions(&temp, std::fs::Permissions::from_mode(0o600))?;\n    }\n    std::fs::rename(&temp, &path).with_context(|| format!(\"failed to publish {}\", path.display()))\n}\n\npub(crate) fn remove_gateway_startup_lease(boot_token: Option<&str>) -> Result<()> {\n    let path = gateway_startup_lease_path()?;\n    let lease = read_gateway_startup_lease()?;\n    if let Some(lease) = lease\n        && let Some(expected) = boot_token\n        && lease.boot_token != expected\n    {\n        anyhow::bail!(\"MCP gateway startup generation changed before cleanup\");\n    }\n    match std::fs::remove_file(&path) {\n        Ok(()) => Ok(()),\n        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),\n        Err(error) => Err(error).with_context(|| format!(\"failed to remove {}\", path.display())),\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]\n#[serde(rename_all = \"snake_case\")]\npub(crate) enum GatewayControlOperation {\n    Health,\n    Stop,\n}\n\n#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]\npub(crate) struct GatewayControlRequest {\n    pub version: u8,","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/mcp/lifecycle.rs#L185-L221","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Skip cleanup — the new generation owns the lease; nothing to do","Ensure gateway startup/shutdown operations are serialized (lock or supervisor) so generations don't overlap","Verify you pass the correct current boot_token from your own startup flow","If the successor is unwanted, stop it and then remove the lease with its own boot_token"],"exampleFix":"// before\nremove_gateway_startup_lease(Some(&stale_boot_token))?;\n// after\nif let Ok(Some(lease)) = read_gateway_startup_lease() {\n    if lease.boot_token == my_boot_token { remove_gateway_startup_lease(Some(&my_boot_token))?; }\n}","handlingStrategy":"try-catch","validationCode":"if let Some(lease) = read_gateway_startup_lease()? {\n    if let Some(expected) = &my_boot_token {\n        if lease.boot_token != *expected { /* successor owns it — skip cleanup */ }\n    }\n}","typeGuard":null,"tryCatchPattern":"match remove_gateway_startup_lease(Some(&boot_token)) {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"generation changed\") => {}, // successor owns the lease; safe to ignore\n    Err(e) => return Err(e),\n}","preventionTips":["Serialize gateway start/stop with a lock or supervisor","Pass your own boot_token so generation checks work","Treat this error as an expected benign outcome in cleanup paths"],"tags":["lifecycle","concurrency","mcp"],"backgroundTag":"invalid-state-transition","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}