diff --git a/src/daemon/launchd-restart-handoff.ts b/src/daemon/launchd-restart-handoff.ts index bae6f156ff5..c26327ab94a 100644 --- a/src/daemon/launchd-restart-handoff.ts +++ b/src/daemon/launchd-restart-handoff.ts @@ -85,6 +85,7 @@ fi `; if (mode === "kickstart") { + // Restart is explicit operator intent; undo any previous `launchctl disable`. return `service_target="$1" domain="$2" plist_path="$3" @@ -98,6 +99,7 @@ fi `; } + // Restart is explicit operator intent; undo any previous `launchctl disable`. return `service_target="$1" domain="$2" plist_path="$3" diff --git a/src/daemon/launchd.ts b/src/daemon/launchd.ts index ef3e13b12d8..a99f4a6c449 100644 --- a/src/daemon/launchd.ts +++ b/src/daemon/launchd.ts @@ -205,6 +205,8 @@ async function bootstrapLaunchAgentOrThrow(params: { plistPath: string; actionHint: string; }) { + // `disable` state survives bootout and plist rewrites; explicit start/repair + // paths must clear it before asking launchd to load the job again. await execLaunchctl(["enable", params.serviceTarget]); const boot = await execLaunchctl(["bootstrap", params.domain, params.plistPath]); if (boot.code === 0) { @@ -504,6 +506,8 @@ type LaunchAgentProbeResult = | { state: "unknown"; detail?: string }; async function probeLaunchAgentState(serviceTarget: string): Promise { + // `launchctl print` output is not a stable API, so this is only a stop + // confirmation probe. Unknown output falls back to bootout instead of success. const probe = await execLaunchctl(["print", serviceTarget]); if (probe.code !== 0) { if (isLaunchctlNotLoaded(probe)) { @@ -545,8 +549,8 @@ export async function stopLaunchAgent({ stdout, env }: GatewayServiceControlArgs const serviceTarget = `${domain}/${label}`; // Keep the LaunchAgent installed, but persistently suppress KeepAlive/RunAtLoad - // before stopping the current process. If disable fails, fall back to bootout so - // the command still leaves the gateway down. + // before stopping the current process. Without `disable`, launchd can relaunch + // the process as soon as `stop` exits. const disable = await execLaunchctl(["disable", serviceTarget]); if (disable.code !== 0) { await bootoutLaunchAgentOrThrow({