Skip to content

Commit 8c82df6

Browse files
committed
fix(gateway): keep unmanaged restarts in-process
1 parent 66b8de9 commit 8c82df6

7 files changed

Lines changed: 45 additions & 28 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Docs: https://docs.openclaw.ai
3636
- CLI/plugins: ship the bundled memory CLI as a package entry so package-installed `openclaw memory` commands register correctly.
3737
- CLI/update: defer doctor-time plugin package installs during package swaps and seed post-core repair from the updated install registry, preventing duplicate reinstall failures.
3838
- Feishu: detect SecretRef top-level credentials as a configured default account instead of treating object-backed app secrets as missing.
39+
- Gateway/restart: keep ordinary unmanaged SIGUSR1/config restarts in-process instead of detach-spawning an orphaned child, preserving custom supervisor PID tracking while leaving update restarts on the fresh-process path. Fixes #65668.
3940
- CLI/completion: resolve concrete PowerShell profile paths and reload commands during setup and doctor completion installation. Fixes #44296. (#83059) Thanks @yu-xin-c.
4041
- Providers/Google: preserve and recover Gemini 3 tool-call thought signatures during native replay so function-calling turns no longer fail with missing `thought_signature` 400s. Fixes #72879. (#80358) Thanks @abnershang.
4142
- Memory-core: distinguish sqlite-vec load failures from missing semantic vector embeddings in degraded `memory index` warnings, so vector recall diagnostics point at unresolved dimensions instead of blaming sqlite-vec when the store is ready. Fixes #75624. (#83056) Thanks @xuruiray and @Noah3521.

docs/install/raspberry-pi.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ EOF
145145
source ~/.bashrc
146146
```
147147

148+
`OPENCLAW_NO_RESPAWN=1` keeps routine Gateway restarts in-process, which avoids extra process handoffs and keeps PID tracking simple on small hosts.
149+
148150
**Reduce memory usage** -- For headless setups, free GPU memory and disable unused services:
149151

150152
```bash

docs/vps.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ source ~/.bashrc
9090
```
9191

9292
- `NODE_COMPILE_CACHE` improves repeated command startup times.
93-
- `OPENCLAW_NO_RESPAWN=1` avoids extra startup overhead from a self-respawn path.
93+
- `OPENCLAW_NO_RESPAWN=1` keeps routine Gateway restarts in-process, which avoids extra process handoffs and keeps PID tracking simple on small hosts.
9494
- First command run warms the cache; subsequent runs are faster.
9595
- For Raspberry Pi specifics, see [Raspberry Pi](/install/raspberry-pi).
9696

src/commands/doctor-platform-notes.startup-optimization.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe("noteStartupOptimizationHints", () => {
3636
expect(message).toBe(
3737
[
3838
"- NODE_COMPILE_CACHE points to /tmp; use /var/tmp so cache survives reboots and warms startup reliably.",
39-
"- OPENCLAW_NO_RESPAWN is not set to 1; set it to avoid extra startup overhead from self-respawn.",
39+
"- OPENCLAW_NO_RESPAWN is not set to 1; set it when you want routine gateway restarts to stay in-process instead of handing off to a managed supervisor.",
4040
"- Suggested env for low-power hosts:",
4141
" export NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache",
4242
" mkdir -p /var/tmp/openclaw-compile-cache",

src/commands/doctor-platform-notes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ export function noteStartupOptimizationHints(
197197

198198
if (noRespawn !== "1") {
199199
lines.push(
200-
"- OPENCLAW_NO_RESPAWN is not set to 1; set it to avoid extra startup overhead from self-respawn.",
200+
"- OPENCLAW_NO_RESPAWN is not set to 1; set it when you want routine gateway restarts to stay in-process instead of handing off to a managed supervisor.",
201201
);
202202
}
203203

src/infra/process-respawn.test.ts

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ describe("restartGatewayProcessWithFreshPid", () => {
133133
expect(spawnMock).not.toHaveBeenCalled();
134134
});
135135

136-
it("spawns detached child with current exec argv", () => {
136+
it("uses in-process restart on unmanaged Unix so custom supervisors keep the tracked PID", () => {
137137
delete process.env.OPENCLAW_NO_RESPAWN;
138138
clearSupervisorHints();
139139
setPlatform("linux");
@@ -143,16 +143,11 @@ describe("restartGatewayProcessWithFreshPid", () => {
143143

144144
const result = restartGatewayProcessWithFreshPid();
145145

146-
expect(result).toEqual({ mode: "spawned", pid: 4242 });
147-
expect(spawnMock).toHaveBeenCalledWith(
148-
process.execPath,
149-
["--import", "tsx", "/repo/dist/index.js", "gateway", "run"],
150-
{
151-
detached: true,
152-
env: process.env,
153-
stdio: "inherit",
154-
},
155-
);
146+
expect(result).toEqual({
147+
mode: "disabled",
148+
detail: "unmanaged: use in-process restart to keep custom supervisor PID tracking stable",
149+
});
150+
expect(spawnMock).not.toHaveBeenCalled();
156151
});
157152

158153
it("returns supervised when OPENCLAW_LAUNCHD_LABEL is set (stock launchd plist)", () => {
@@ -186,12 +181,15 @@ describe("restartGatewayProcessWithFreshPid", () => {
186181
setPlatform("linux");
187182
process.env.OPENCLAW_SERVICE_MARKER = "openclaw";
188183
process.env.OPENCLAW_SERVICE_KIND = "gateway";
189-
spawnMock.mockReturnValue({ pid: 4242, unref: vi.fn() });
190184

191185
const result = restartGatewayProcessWithFreshPid();
192186

193-
expect(result).toEqual({ mode: "spawned", pid: 4242 });
187+
expect(result).toEqual({
188+
mode: "disabled",
189+
detail: "unmanaged: use in-process restart to keep custom supervisor PID tracking stable",
190+
});
194191
expect(triggerOpenClawRestartMock).not.toHaveBeenCalled();
192+
expect(spawnMock).not.toHaveBeenCalled();
195193
});
196194

197195
it("returns disabled on Windows without Scheduled Task markers", () => {
@@ -235,7 +233,7 @@ describe("restartGatewayProcessWithFreshPid", () => {
235233
expect(spawnMock).not.toHaveBeenCalled();
236234
});
237235

238-
it("returns failed when spawn throws", () => {
236+
it("does not attempt detached spawn on unmanaged Unix even if spawn would throw", () => {
239237
delete process.env.OPENCLAW_NO_RESPAWN;
240238
clearSupervisorHints();
241239
setPlatform("linux");
@@ -244,8 +242,11 @@ describe("restartGatewayProcessWithFreshPid", () => {
244242
throw new Error("spawn failed");
245243
});
246244
const result = restartGatewayProcessWithFreshPid();
247-
expect(result.mode).toBe("failed");
248-
expect(result.detail).toContain("spawn failed");
245+
expect(result).toEqual({
246+
mode: "disabled",
247+
detail: "unmanaged: use in-process restart to keep custom supervisor PID tracking stable",
248+
});
249+
expect(spawnMock).not.toHaveBeenCalled();
249250
});
250251
});
251252

@@ -286,4 +287,19 @@ describe("respawnGatewayProcessForUpdate", () => {
286287
},
287288
);
288289
});
290+
291+
it("returns failed when update detached respawn throws", () => {
292+
delete process.env.OPENCLAW_NO_RESPAWN;
293+
clearSupervisorHints();
294+
setPlatform("linux");
295+
296+
spawnMock.mockImplementation(() => {
297+
throw new Error("spawn failed");
298+
});
299+
300+
const result = respawnGatewayProcessForUpdate();
301+
302+
expect(result.mode).toBe("failed");
303+
expect(result.detail).toContain("spawn failed");
304+
});
289305
});

src/infra/process-respawn.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ function spawnDetachedGatewayProcess(opts: GatewayRespawnOptions = {}): {
4343
* Attempt to restart this process with a fresh PID.
4444
* - supervised environments (launchd/systemd/schtasks): caller should exit and let supervisor restart
4545
* - OPENCLAW_NO_RESPAWN=1: caller should keep in-process restart behavior (tests/dev)
46-
* - otherwise: spawn detached child with current argv/execArgv, then caller exits
46+
* - unmanaged environments: caller should keep in-process restart behavior so
47+
* custom supervisors keep tracking the same gateway PID
4748
*/
4849
export function restartGatewayProcessWithFreshPid(
49-
opts: GatewayRespawnOptions = {},
50+
_opts: GatewayRespawnOptions = {},
5051
): GatewayRespawnResult {
5152
if (isTruthy(process.env.OPENCLAW_NO_RESPAWN)) {
5253
return { mode: "disabled" };
@@ -82,13 +83,10 @@ export function restartGatewayProcessWithFreshPid(
8283
};
8384
}
8485

85-
try {
86-
const { pid } = spawnDetachedGatewayProcess(opts);
87-
return { mode: "spawned", pid };
88-
} catch (err) {
89-
const detail = formatErrorMessage(err);
90-
return { mode: "failed", detail };
91-
}
86+
return {
87+
mode: "disabled",
88+
detail: "unmanaged: use in-process restart to keep custom supervisor PID tracking stable",
89+
};
9290
}
9391

9492
/**

0 commit comments

Comments
 (0)