fix(infra): actively kickstart launchd on supervised gateway restart

When an agent triggers a gateway restart in supervised mode, the process
exits expecting launchd KeepAlive to respawn it. But ThrottleInterval
(default 10s, or 60s on older installs) can delay or prevent restart.

Now calls triggerOpenClawRestart() to issue an explicit launchctl
kickstart before exiting, ensuring immediate respawn. Falls back to
in-process restart if kickstart fails.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Cathryn Lavery
2026-02-27 13:19:40 -06:00
committed by Peter Steinberger
parent ee2eaddeb3
commit db67492a00
2 changed files with 98 additions and 1 deletions

View File

@@ -21,6 +21,29 @@ function isLikelySupervisedProcess(env: NodeJS.ProcessEnv = process.env): boolea
return hasSupervisorHint(env);
}
/**
* Spawn a detached `launchctl kickstart -k` to force an immediate launchd
* restart, bypassing ThrottleInterval. The -k flag sends SIGTERM to the
* current process, so this MUST be non-blocking (spawn, not spawnSync) to
* avoid deadlocking — the gateway needs to be free to handle the signal
* and exit so launchd can start the replacement.
*/
function schedulelaunchdKickstart(label: string): boolean {
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
const target = uid !== undefined ? `gui/${uid}/${label}` : label;
try {
const child = spawn("launchctl", ["kickstart", "-k", target], {
detached: true,
stdio: "ignore",
});
child.on("error", () => {}); // best-effort; suppress uncaught error event
child.unref();
return true;
} catch {
return false;
}
}
/**
* Attempt to restart this process with a fresh PID.
* - supervised environments (launchd/systemd): caller should exit and let supervisor restart
@@ -32,6 +55,11 @@ export function restartGatewayProcessWithFreshPid(): GatewayRespawnResult {
return { mode: "disabled" };
}
if (isLikelySupervisedProcess(process.env)) {
// On macOS under launchd, fire a detached kickstart so launchd restarts
// us immediately instead of waiting for ThrottleInterval (up to 60s).
if (process.platform === "darwin" && process.env.OPENCLAW_LAUNCHD_LABEL?.trim()) {
schedulelaunchdKickstart(process.env.OPENCLAW_LAUNCHD_LABEL.trim());
}
return { mode: "supervised" };
}