diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index d04dd52e..a8fa81ab 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -1306,6 +1306,17 @@ def _run_gateway( raise typer.Exit(1) from exc session_manager = SessionManager(config.workspace_path) + # Self-heal the gateway state file with the current PID after any restart. + from nanobot.gateway.runtime import GatewayRuntime, GatewayRuntimePaths + GatewayRuntime.refresh_state_pid( + paths=GatewayRuntimePaths.for_instance( + workspace=str(config.workspace_path) + if not is_default_workspace(config.workspace_path) + else None, + config_path=config.config_path, + ) + ) + # Preserve existing single-workspace installs, but keep custom workspaces clean. if is_default_workspace(config.workspace_path): _migrate_cron_store(config) diff --git a/nanobot/gateway/runtime.py b/nanobot/gateway/runtime.py index 7acd878f..bff59f5c 100644 --- a/nanobot/gateway/runtime.py +++ b/nanobot/gateway/runtime.py @@ -127,6 +127,25 @@ class GatewayRuntime: self._subprocess_run = subprocess_run self._sleep = sleep + @classmethod + def refresh_state_pid(cls, *, paths: GatewayRuntimePaths) -> None: + """Update the PID in an existing state file to ``os.getpid()``. + + Called early in gateway server startup so the state file self-heals + after any restart, regardless of platform or restart mechanism. + """ + if not paths.state_path.exists(): + return + try: + state = json.loads(paths.state_path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError): + return + state["pid"] = os.getpid() + rt = cls(paths=paths) + state["identity"] = rt._process_identity(os.getpid()) + state["started_at"] = _utc_now() + rt._write_state(state) + def start_background(self, options: GatewayStartOptions) -> RuntimeResult: """Start gateway as a detached background process.""" current = self.status()