mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix(gateway): keep unmanaged restarts in-process (#83138)
Summary: - The PR changes ordinary unmanaged gateway restarts to return the existing in-process fallback instead of detached-spawning a replacement child, with focused tests, docs wording, and a changelog entry. - Reproducibility: yes. at source level: current main and v2026.5.12 detach-spawn unmanaged ordinary restarts, ... e PR body also supplies after-fix terminal proof that the patched helper returns disabled without spawning. Automerge notes: - No ClawSweeper repair was needed after automerge opt-in. Validation: - ClawSweeper review passed for head8c82df6c77. - Required merge gates passed before the squash merge. Prepared head SHA:8c82df6c77Review: https://github.com/openclaw/openclaw/pull/83138#issuecomment-4471071848 Co-authored-by: mjamiv <74088820+mjamiv@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -46,6 +46,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- CLI/plugins: ship the bundled memory CLI as a package entry so package-installed `openclaw memory` commands register correctly.
|
- CLI/plugins: ship the bundled memory CLI as a package entry so package-installed `openclaw memory` commands register correctly.
|
||||||
- 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.
|
- 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.
|
||||||
- Feishu: detect SecretRef top-level credentials as a configured default account instead of treating object-backed app secrets as missing.
|
- Feishu: detect SecretRef top-level credentials as a configured default account instead of treating object-backed app secrets as missing.
|
||||||
|
- 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.
|
||||||
- CLI/completion: resolve concrete PowerShell profile paths and reload commands during setup and doctor completion installation. Fixes #44296. (#83059) Thanks @yu-xin-c.
|
- CLI/completion: resolve concrete PowerShell profile paths and reload commands during setup and doctor completion installation. Fixes #44296. (#83059) Thanks @yu-xin-c.
|
||||||
- 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.
|
- 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.
|
||||||
- Memory/QMD: keep lexical search on raw hyphenated queries while normalizing semantic QMD sub-searches, avoiding fallback to the builtin index for dashed identifiers and dates. Fixes #81328.
|
- Memory/QMD: keep lexical search on raw hyphenated queries while normalizing semantic QMD sub-searches, avoiding fallback to the builtin index for dashed identifiers and dates. Fixes #81328.
|
||||||
|
|||||||
@@ -145,6 +145,8 @@ EOF
|
|||||||
source ~/.bashrc
|
source ~/.bashrc
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`OPENCLAW_NO_RESPAWN=1` keeps routine Gateway restarts in-process, which avoids extra process handoffs and keeps PID tracking simple on small hosts.
|
||||||
|
|
||||||
**Reduce memory usage** -- For headless setups, free GPU memory and disable unused services:
|
**Reduce memory usage** -- For headless setups, free GPU memory and disable unused services:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ source ~/.bashrc
|
|||||||
```
|
```
|
||||||
|
|
||||||
- `NODE_COMPILE_CACHE` improves repeated command startup times.
|
- `NODE_COMPILE_CACHE` improves repeated command startup times.
|
||||||
- `OPENCLAW_NO_RESPAWN=1` avoids extra startup overhead from a self-respawn path.
|
- `OPENCLAW_NO_RESPAWN=1` keeps routine Gateway restarts in-process, which avoids extra process handoffs and keeps PID tracking simple on small hosts.
|
||||||
- First command run warms the cache; subsequent runs are faster.
|
- First command run warms the cache; subsequent runs are faster.
|
||||||
- For Raspberry Pi specifics, see [Raspberry Pi](/install/raspberry-pi).
|
- For Raspberry Pi specifics, see [Raspberry Pi](/install/raspberry-pi).
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ describe("noteStartupOptimizationHints", () => {
|
|||||||
expect(message).toBe(
|
expect(message).toBe(
|
||||||
[
|
[
|
||||||
"- NODE_COMPILE_CACHE points to /tmp; use /var/tmp so cache survives reboots and warms startup reliably.",
|
"- NODE_COMPILE_CACHE points to /tmp; use /var/tmp so cache survives reboots and warms startup reliably.",
|
||||||
"- OPENCLAW_NO_RESPAWN is not set to 1; set it to avoid extra startup overhead from self-respawn.",
|
"- 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.",
|
||||||
"- Suggested env for low-power hosts:",
|
"- Suggested env for low-power hosts:",
|
||||||
" export NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache",
|
" export NODE_COMPILE_CACHE=/var/tmp/openclaw-compile-cache",
|
||||||
" mkdir -p /var/tmp/openclaw-compile-cache",
|
" mkdir -p /var/tmp/openclaw-compile-cache",
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ export function noteStartupOptimizationHints(
|
|||||||
|
|
||||||
if (noRespawn !== "1") {
|
if (noRespawn !== "1") {
|
||||||
lines.push(
|
lines.push(
|
||||||
"- OPENCLAW_NO_RESPAWN is not set to 1; set it to avoid extra startup overhead from self-respawn.",
|
"- 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.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ describe("restartGatewayProcessWithFreshPid", () => {
|
|||||||
expect(spawnMock).not.toHaveBeenCalled();
|
expect(spawnMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("spawns detached child with current exec argv", () => {
|
it("uses in-process restart on unmanaged Unix so custom supervisors keep the tracked PID", () => {
|
||||||
delete process.env.OPENCLAW_NO_RESPAWN;
|
delete process.env.OPENCLAW_NO_RESPAWN;
|
||||||
clearSupervisorHints();
|
clearSupervisorHints();
|
||||||
setPlatform("linux");
|
setPlatform("linux");
|
||||||
@@ -143,16 +143,11 @@ describe("restartGatewayProcessWithFreshPid", () => {
|
|||||||
|
|
||||||
const result = restartGatewayProcessWithFreshPid();
|
const result = restartGatewayProcessWithFreshPid();
|
||||||
|
|
||||||
expect(result).toEqual({ mode: "spawned", pid: 4242 });
|
expect(result).toEqual({
|
||||||
expect(spawnMock).toHaveBeenCalledWith(
|
mode: "disabled",
|
||||||
process.execPath,
|
detail: "unmanaged: use in-process restart to keep custom supervisor PID tracking stable",
|
||||||
["--import", "tsx", "/repo/dist/index.js", "gateway", "run"],
|
});
|
||||||
{
|
expect(spawnMock).not.toHaveBeenCalled();
|
||||||
detached: true,
|
|
||||||
env: process.env,
|
|
||||||
stdio: "inherit",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns supervised when OPENCLAW_LAUNCHD_LABEL is set (stock launchd plist)", () => {
|
it("returns supervised when OPENCLAW_LAUNCHD_LABEL is set (stock launchd plist)", () => {
|
||||||
@@ -186,12 +181,15 @@ describe("restartGatewayProcessWithFreshPid", () => {
|
|||||||
setPlatform("linux");
|
setPlatform("linux");
|
||||||
process.env.OPENCLAW_SERVICE_MARKER = "openclaw";
|
process.env.OPENCLAW_SERVICE_MARKER = "openclaw";
|
||||||
process.env.OPENCLAW_SERVICE_KIND = "gateway";
|
process.env.OPENCLAW_SERVICE_KIND = "gateway";
|
||||||
spawnMock.mockReturnValue({ pid: 4242, unref: vi.fn() });
|
|
||||||
|
|
||||||
const result = restartGatewayProcessWithFreshPid();
|
const result = restartGatewayProcessWithFreshPid();
|
||||||
|
|
||||||
expect(result).toEqual({ mode: "spawned", pid: 4242 });
|
expect(result).toEqual({
|
||||||
|
mode: "disabled",
|
||||||
|
detail: "unmanaged: use in-process restart to keep custom supervisor PID tracking stable",
|
||||||
|
});
|
||||||
expect(triggerOpenClawRestartMock).not.toHaveBeenCalled();
|
expect(triggerOpenClawRestartMock).not.toHaveBeenCalled();
|
||||||
|
expect(spawnMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns disabled on Windows without Scheduled Task markers", () => {
|
it("returns disabled on Windows without Scheduled Task markers", () => {
|
||||||
@@ -235,7 +233,7 @@ describe("restartGatewayProcessWithFreshPid", () => {
|
|||||||
expect(spawnMock).not.toHaveBeenCalled();
|
expect(spawnMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns failed when spawn throws", () => {
|
it("does not attempt detached spawn on unmanaged Unix even if spawn would throw", () => {
|
||||||
delete process.env.OPENCLAW_NO_RESPAWN;
|
delete process.env.OPENCLAW_NO_RESPAWN;
|
||||||
clearSupervisorHints();
|
clearSupervisorHints();
|
||||||
setPlatform("linux");
|
setPlatform("linux");
|
||||||
@@ -244,8 +242,11 @@ describe("restartGatewayProcessWithFreshPid", () => {
|
|||||||
throw new Error("spawn failed");
|
throw new Error("spawn failed");
|
||||||
});
|
});
|
||||||
const result = restartGatewayProcessWithFreshPid();
|
const result = restartGatewayProcessWithFreshPid();
|
||||||
expect(result.mode).toBe("failed");
|
expect(result).toEqual({
|
||||||
expect(result.detail).toContain("spawn failed");
|
mode: "disabled",
|
||||||
|
detail: "unmanaged: use in-process restart to keep custom supervisor PID tracking stable",
|
||||||
|
});
|
||||||
|
expect(spawnMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -286,4 +287,19 @@ describe("respawnGatewayProcessForUpdate", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns failed when update detached respawn throws", () => {
|
||||||
|
delete process.env.OPENCLAW_NO_RESPAWN;
|
||||||
|
clearSupervisorHints();
|
||||||
|
setPlatform("linux");
|
||||||
|
|
||||||
|
spawnMock.mockImplementation(() => {
|
||||||
|
throw new Error("spawn failed");
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = respawnGatewayProcessForUpdate();
|
||||||
|
|
||||||
|
expect(result.mode).toBe("failed");
|
||||||
|
expect(result.detail).toContain("spawn failed");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,10 +43,11 @@ function spawnDetachedGatewayProcess(opts: GatewayRespawnOptions = {}): {
|
|||||||
* Attempt to restart this process with a fresh PID.
|
* Attempt to restart this process with a fresh PID.
|
||||||
* - supervised environments (launchd/systemd/schtasks): caller should exit and let supervisor restart
|
* - supervised environments (launchd/systemd/schtasks): caller should exit and let supervisor restart
|
||||||
* - OPENCLAW_NO_RESPAWN=1: caller should keep in-process restart behavior (tests/dev)
|
* - OPENCLAW_NO_RESPAWN=1: caller should keep in-process restart behavior (tests/dev)
|
||||||
* - otherwise: spawn detached child with current argv/execArgv, then caller exits
|
* - unmanaged environments: caller should keep in-process restart behavior so
|
||||||
|
* custom supervisors keep tracking the same gateway PID
|
||||||
*/
|
*/
|
||||||
export function restartGatewayProcessWithFreshPid(
|
export function restartGatewayProcessWithFreshPid(
|
||||||
opts: GatewayRespawnOptions = {},
|
_opts: GatewayRespawnOptions = {},
|
||||||
): GatewayRespawnResult {
|
): GatewayRespawnResult {
|
||||||
if (isTruthy(process.env.OPENCLAW_NO_RESPAWN)) {
|
if (isTruthy(process.env.OPENCLAW_NO_RESPAWN)) {
|
||||||
return { mode: "disabled" };
|
return { mode: "disabled" };
|
||||||
@@ -82,13 +83,10 @@ export function restartGatewayProcessWithFreshPid(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
return {
|
||||||
const { pid } = spawnDetachedGatewayProcess(opts);
|
mode: "disabled",
|
||||||
return { mode: "spawned", pid };
|
detail: "unmanaged: use in-process restart to keep custom supervisor PID tracking stable",
|
||||||
} catch (err) {
|
};
|
||||||
const detail = formatErrorMessage(err);
|
|
||||||
return { mode: "failed", detail };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user