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
- 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
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
- 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
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
- MCP gateway readiness changed before cleanup at {}
- MCP gateway is shutting down
- MCP gateway attach limit reached for principal '{principal}'
- invalid MCP gateway startup lease at {}
- invalid MCP gateway readiness metadata at {}
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/b399f07129daedf0.
Report an issue: GitHub.