From cf796e2a223f90902fcd122e3c797acf20990255 Mon Sep 17 00:00:00 2001 From: dimatu Date: Thu, 19 Feb 2026 02:54:18 +0000 Subject: [PATCH] fix(gateway): detect launchd supervision via XPC_SERVICE_NAME On macOS, launchd sets XPC_SERVICE_NAME on managed processes but does not set LAUNCH_JOB_LABEL or LAUNCH_JOB_NAME. Without checking XPC_SERVICE_NAME, isLikelySupervisedProcess() returns false for launchd-managed gateways, causing restartGatewayProcessWithFreshPid() to fork a detached child instead of returning "supervised". The detached child holds the gateway lock while launchd simultaneously respawns the original process (KeepAlive=true), leading to an infinite lock-timeout / restart loop. Co-Authored-By: Claude Opus 4.6 --- src/infra/process-respawn.test.ts | 10 ++++++++++ src/infra/supervisor-markers.ts | 1 + 2 files changed, 11 insertions(+) diff --git a/src/infra/process-respawn.test.ts b/src/infra/process-respawn.test.ts index 62091423af7..7b9a9df1252 100644 --- a/src/infra/process-respawn.test.ts +++ b/src/infra/process-respawn.test.ts @@ -108,6 +108,16 @@ describe("restartGatewayProcessWithFreshPid", () => { expect(spawnMock).not.toHaveBeenCalled(); }); + it("returns supervised when XPC_SERVICE_NAME is set by launchd", () => { + clearSupervisorHints(); + setPlatform("darwin"); + process.env.XPC_SERVICE_NAME = "ai.openclaw.gateway"; + const result = restartGatewayProcessWithFreshPid(); + expect(result.mode).toBe("supervised"); + expect(triggerOpenClawRestartMock).not.toHaveBeenCalled(); + expect(spawnMock).not.toHaveBeenCalled(); + }); + it("spawns detached child with current exec argv", () => { delete process.env.OPENCLAW_NO_RESPAWN; clearSupervisorHints(); diff --git a/src/infra/supervisor-markers.ts b/src/infra/supervisor-markers.ts index f024ddeca2e..5b714735724 100644 --- a/src/infra/supervisor-markers.ts +++ b/src/infra/supervisor-markers.ts @@ -1,6 +1,7 @@ const LAUNCHD_SUPERVISOR_HINT_ENV_VARS = [ "LAUNCH_JOB_LABEL", "LAUNCH_JOB_NAME", + "XPC_SERVICE_NAME", "OPENCLAW_LAUNCHD_LABEL", ] as const;