From d2248534d85bd3829d6a3a255f948cd25e7a79d0 Mon Sep 17 00:00:00 2001 From: HCL Date: Wed, 25 Mar 2026 07:39:05 +0800 Subject: [PATCH] fix(daemon): bootstrap stopped service on `gateway start` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After `gateway stop` (which runs `launchctl bootout`), `gateway start` checks `isLoaded` → false → prints "not loaded" hints and exits. The service is never re-bootstrapped, so `start` cannot recover from `stop` — only `gateway install` works. Root cause: src/cli/daemon-cli/lifecycle-core.ts:208-217 — runServiceStart calls handleServiceNotLoaded which only prints hints, never attempts service.restart() (which already handles bootstrap via bootstrapLaunchAgentOrThrow at launchd.ts:598). Fix: when service is not loaded, attempt service.restart() first (which handles re-bootstrapping on all platforms). If restart fails (e.g. plist was deleted, not just booted out), fall back to the existing hints. The restart path is already proven: restartLaunchAgent (launchd.ts:556) handles "not loaded" via bootstrapLaunchAgentOrThrow. This fix routes the start command through the same recovery path. Closes #53878 Co-Authored-By: Claude Opus 4.6 Signed-off-by: HCL --- src/cli/daemon-cli/lifecycle-core.ts | 38 +++++++++++++++++++++------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/cli/daemon-cli/lifecycle-core.ts b/src/cli/daemon-cli/lifecycle-core.ts index e3cf107e0ae..83264e99932 100644 --- a/src/cli/daemon-cli/lifecycle-core.ts +++ b/src/cli/daemon-cli/lifecycle-core.ts @@ -209,15 +209,35 @@ export async function runServiceStart(params: { return; } if (!loaded) { - await handleServiceNotLoaded({ - serviceNoun: params.serviceNoun, - service: params.service, - loaded, - renderStartHints: params.renderStartHints, - json, - emit, - }); - return; + // Service was stopped (e.g. `gateway stop` booted out the LaunchAgent). + // Attempt a restart, which handles re-bootstrapping the service. Without + // this, `start` after `stop` just prints hints and does nothing (#53878). + try { + const restartResult = await params.service.restart({ env: process.env, stdout }); + const restartStatus = describeGatewayServiceRestart(params.serviceNoun, restartResult); + emit({ + ok: true, + result: restartStatus.daemonActionResult, + message: restartStatus.message, + service: buildDaemonServiceSnapshot(params.service, true), + }); + if (!json) { + defaultRuntime.log(restartStatus.message); + } + return; + } catch { + // Bootstrap failed (e.g. plist was deleted, not just booted out). + // Fall through to the not-loaded hints. + await handleServiceNotLoaded({ + serviceNoun: params.serviceNoun, + service: params.service, + loaded, + renderStartHints: params.renderStartHints, + json, + emit, + }); + return; + } } // Pre-flight config validation (#35862) {