Skip to content

Commit 792ce7b

Browse files
taw0002steipete
authored andcommitted
fix: detect OpenClaw-managed launchd/systemd services in process respawn
restartGatewayProcessWithFreshPid() checks SUPERVISOR_HINT_ENV_VARS to decide whether to let the supervisor handle the restart (mode=supervised) or to fork a detached child (mode=spawned). The existing list only had native launchd vars (LAUNCH_JOB_LABEL, LAUNCH_JOB_NAME) and systemd vars (INVOCATION_ID, SYSTEMD_EXEC_PID, JOURNAL_STREAM). macOS launchd does NOT automatically inject LAUNCH_JOB_LABEL into the child environment. OpenClaw's own plist generator (buildServiceEnvironment in service-env.ts) sets OPENCLAW_LAUNCHD_LABEL instead. So on stock macOS LaunchAgent installs, isLikelySupervisedProcess() returned false, causing the gateway to fork a detached child on SIGUSR1 restart. The original process then exits, launchd sees its child died, respawns a new instance which finds the orphan holding the port — infinite crash loop. Fix: add OPENCLAW_LAUNCHD_LABEL, OPENCLAW_SYSTEMD_UNIT, and OPENCLAW_SERVICE_MARKER to the supervisor hint list. These are set by OpenClaw's own service environment builders for both launchd and systemd and are the reliable supervised-mode signals. Fixes #27605
1 parent 5c02554 commit 792ce7b

File tree

2 files changed

+37
-0
lines changed

2 files changed

+37
-0
lines changed

src/infra/process-respawn.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ afterEach(() => {
2323
function clearSupervisorHints() {
2424
delete process.env.LAUNCH_JOB_LABEL;
2525
delete process.env.LAUNCH_JOB_NAME;
26+
delete process.env.OPENCLAW_LAUNCHD_LABEL;
2627
delete process.env.INVOCATION_ID;
2728
delete process.env.SYSTEMD_EXEC_PID;
2829
delete process.env.JOURNAL_STREAM;
30+
delete process.env.OPENCLAW_SYSTEMD_UNIT;
31+
delete process.env.OPENCLAW_SERVICE_MARKER;
2932
}
3033

3134
describe("restartGatewayProcessWithFreshPid", () => {
@@ -63,6 +66,30 @@ describe("restartGatewayProcessWithFreshPid", () => {
6366
);
6467
});
6568

69+
it("returns supervised when OPENCLAW_LAUNCHD_LABEL is set (stock launchd plist)", () => {
70+
clearSupervisorHints();
71+
process.env.OPENCLAW_LAUNCHD_LABEL = "ai.openclaw.gateway";
72+
const result = restartGatewayProcessWithFreshPid();
73+
expect(result.mode).toBe("supervised");
74+
expect(spawnMock).not.toHaveBeenCalled();
75+
});
76+
77+
it("returns supervised when OPENCLAW_SYSTEMD_UNIT is set", () => {
78+
clearSupervisorHints();
79+
process.env.OPENCLAW_SYSTEMD_UNIT = "openclaw-gateway.service";
80+
const result = restartGatewayProcessWithFreshPid();
81+
expect(result.mode).toBe("supervised");
82+
expect(spawnMock).not.toHaveBeenCalled();
83+
});
84+
85+
it("returns supervised when OPENCLAW_SERVICE_MARKER is set", () => {
86+
clearSupervisorHints();
87+
process.env.OPENCLAW_SERVICE_MARKER = "gateway";
88+
const result = restartGatewayProcessWithFreshPid();
89+
expect(result.mode).toBe("supervised");
90+
expect(spawnMock).not.toHaveBeenCalled();
91+
});
92+
6693
it("returns failed when spawn throws", () => {
6794
delete process.env.OPENCLAW_NO_RESPAWN;
6895
clearSupervisorHints();

src/infra/process-respawn.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,21 @@ export type GatewayRespawnResult = {
99
};
1010

1111
const SUPERVISOR_HINT_ENV_VARS = [
12+
// macOS launchd — native env vars (may be set by launchd itself)
1213
"LAUNCH_JOB_LABEL",
1314
"LAUNCH_JOB_NAME",
15+
// macOS launchd — OpenClaw's own plist generator sets these via
16+
// buildServiceEnvironment() in service-env.ts. launchd does NOT
17+
// automatically inject LAUNCH_JOB_LABEL into the child environment,
18+
// so OPENCLAW_LAUNCHD_LABEL is the reliable supervised-mode signal.
19+
"OPENCLAW_LAUNCHD_LABEL",
20+
// Linux systemd
1421
"INVOCATION_ID",
1522
"SYSTEMD_EXEC_PID",
1623
"JOURNAL_STREAM",
24+
"OPENCLAW_SYSTEMD_UNIT",
25+
// Generic service marker (set by both launchd and systemd plist/unit generators)
26+
"OPENCLAW_SERVICE_MARKER",
1727
];
1828

1929
function isTruthy(value: string | undefined): boolean {

0 commit comments

Comments
 (0)