{"record":{"id":"715904771b1d1cad","repo":"astrid-runtime/astrid","slug":"shutdown-stage-gateway-startup-identity-startup-g","errorCode":null,"errorMessage":"shutdown stage gateway.startup_identity: startup generation changed","messagePattern":"shutdown stage gateway\\.startup_identity: startup generation changed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/mcp/lifecycle.rs","lineNumber":608,"sourceCode":"            \"shutdown stage gateway.process_reap: authenticated gateway PID {} did not exit\",\n            record.pid\n        );\n    }\n    remove_dead_gateway_markers(&record).await\n}\n\nasync fn stop_startup_gateway(lease: &GatewayStartupLease) -> Result<()> {\n    let Some(gateway_pid) = lease.gateway_pid else {\n        anyhow::bail!(\"shutdown stage gateway.startup_identity: lease has no gateway PID\");\n    };\n    let Some(gateway_exe) = lease.gateway_exe.as_deref() else {\n        anyhow::bail!(\"shutdown stage gateway.startup_identity: lease has no executable\");\n    };\n    let current = read_gateway_startup_lease()?.ok_or_else(|| {\n        anyhow::anyhow!(\"shutdown stage gateway.startup_identity: lease disappeared\")\n    })?;\n    if current != *lease {\n        anyhow::bail!(\"shutdown stage gateway.startup_identity: startup generation changed\");\n    }\n\n    let outcome =\n        crate::commands::daemon_control::terminate_known(gateway_pid, Some(gateway_exe)).await;\n    if !matches!(\n        outcome,\n        crate::commands::daemon_control::KillOutcome::TermExited\n            | crate::commands::daemon_control::KillOutcome::KilledExited\n    ) {\n        anyhow::bail!(\n            \"shutdown stage gateway.startup_reap: starting gateway PID {gateway_pid} is {outcome:?}\"\n        );\n    }\n    Ok(())\n}\n\nasync fn clean_unowned_gateway_startup(lifecycle: &GatewayLifecycleLock) -> Result<()> {\n    let socket = gateway_socket_path()?;","sourceCodeStart":590,"sourceCodeEnd":626,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/mcp/lifecycle.rs#L590-L626","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-read the current lease and only stop the gateway you actually own (retry the stop against the newest lease)","Serialize startup/shutdown with a lock so only one generation exists at a time","If the old gateway is harmless, skip the stop — its markers are now owned by the new generation","Check for duplicate cron/systemd units or IDE extensions each starting the gateway"],"exampleFix":"// before\nstop_gateway().await?;\n// after: tolerate generation change\nmatch stop_gateway().await {\n    Err(e) if e.to_string().contains(\"startup generation changed\") => {\n        // newer gateway owns the lease; nothing to do\n    },\n    other => other?,\n}","handlingStrategy":"retry","validationCode":"let on_disk = read_gateway_startup_lease()?;\nif on_disk.as_ref() != Some(&my_lease) {\n    // generation already replaced; skip the stop\n    return Ok(());\n}","typeGuard":"fn lease_is_current(mine: &GatewayStartupLease) -> bool {\n    read_gateway_startup_lease()\n        .ok()\n        .flatten()\n        .map(|cur| cur == *mine)\n        .unwrap_or(false)\n}","tryCatchPattern":"match stop_gateway().await {\n    Err(e) if e.to_string().contains(\"startup generation changed\") => {\n        // benign: a newer generation owns the lease\n    },\n    other => other?,\n}","preventionTips":["Serialize gateway start/stop behind a single lock or supervisor","Avoid running multiple astrid clients against one state directory","Re-read the lease immediately before stopping"],"tags":["race-condition","process-management","concurrency"],"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-14T05:17:10.506Z"}