{"record":{"id":"8b09c1b02d680859","repo":"astrid-runtime/astrid","slug":"shutdown-stage-gateway-startup-stop-lifecycle-cha","errorCode":null,"errorMessage":"shutdown stage gateway.startup_stop: lifecycle changed","messagePattern":"shutdown stage gateway\\.startup_stop: lifecycle changed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/mcp/lifecycle.rs","lineNumber":547,"sourceCode":"            }\n            if let Some(lease) = read_gateway_startup_lease()?\n                && lease\n                    .gateway_pid\n                    .is_some_and(crate::commands::daemon_control::is_process_alive)\n            {\n                stop_startup_gateway(&lease).await?;\n                continue;\n            }\n            if Instant::now() >= deadline {\n                anyhow::bail!(\n                    \"shutdown stage gateway.startup_stop: a starting gateway did not become stoppable within {} seconds\",\n                    READY_TIMEOUT.as_secs()\n                );\n            }\n            tokio::time::sleep(READY_POLL).await;\n        }\n        let lifecycle = try_acquire_gateway_lifecycle()?.ok_or_else(|| {\n            anyhow::anyhow!(\"shutdown stage gateway.startup_stop: lifecycle changed\")\n        })?;\n        clean_unowned_gateway_startup(&lifecycle)\n            .await\n            .context(\"shutdown stage gateway.stale_listener_cleanup\")?;\n        drop(lifecycle);\n        return Ok(());\n    };\n    stop_ready_gateway(record, socket).await\n}\n\nasync fn stop_ready_gateway(record: GatewayReady, socket: PathBuf) -> Result<()> {\n    let stream = match astrid_core::local_transport::connect_outcome(&socket)\n        .await\n        .context(\"shutdown stage gateway.listener_probe\")?\n    {\n        astrid_core::local_transport::ConnectOutcome::Connected(stream) => stream,\n        astrid_core::local_transport::ConnectOutcome::Absent\n        | astrid_core::local_transport::ConnectOutcome::Stale","sourceCodeStart":529,"sourceCodeEnd":565,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/mcp/lifecycle.rs#L529-L565","documentation":"During `stop_gateway`, if the shutdown discovers the gateway is still in its startup phase, it waits for readiness and then re-acquires the lifecycle lock to clean up the startup artifacts. This error is thrown when that re-acquisition fails (returns `None`), meaning the lock was taken by someone else between the check and the reacquire — the lifecycle changed mid-shutdown (e.g. a new gateway started, or another stop won the race).","triggerScenarios":"Concurrent `gateway run` (or a supervisor restart) grabbing the lifecycle lock exactly while `stop_gateway` is waiting through the ready-poll loop; two concurrent `stop_gateway` invocations racing.","commonSituations":"A daemon supervisor auto-restarting the gateway during a manual stop; scripts running start and stop simultaneously; a user starting the gateway in one terminal while another stops it.","solutions":["Re-run the stop command; the racing operation has completed and the current state is likely what you want.","Check whether a new gateway instance started during shutdown (check socket/ready file) and decide whether to stop it again.","Avoid running concurrent start/stop operations; serialize lifecycle commands in scripts.","Disable or pause auto-restart supervisors (systemd unit, daemon watchdog) while manually stopping."],"exampleFix":"// before\nastrid mcp gateway stop   # may race with restart\n// after\nsystemctl --user stop astrid-gateway   # pause supervisor first\nastrid mcp gateway stop","handlingStrategy":"retry","validationCode":"// Before stopping, check no start is in progress\nif startup_lease_exists() {\n    eprintln!(\"gateway is starting; wait for readiness before stopping\");\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = stop_gateway().await {\n    if e.to_string().contains(\"lifecycle changed\") {\n        eprintln!(\"lifecycle raced; retrying stop\");\n        stop_gateway().await?;\n    } else { return Err(e); }\n}","preventionTips":["Serialize gateway start/stop commands in scripts","Pause auto-restart supervisors before manual stops","Wait for readiness before issuing stop during startup","Run only one lifecycle command at a time"],"tags":["mcp","lifecycle","race-condition","lock-contention"],"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-17T15:17:12.973Z"}