fix(gateway): keep watch restarts in-process

This commit is contained in:
Peter Steinberger
2026-04-05 13:15:18 +01:00
parent 2f5d6c859d
commit aca488d5be
3 changed files with 49 additions and 0 deletions

View File

@@ -44,6 +44,9 @@ export async function runWatchMain(params = {}) {
const watchSession = `${deps.now()}-${deps.process.pid}`;
childEnv.OPENCLAW_WATCH_MODE = "1";
childEnv.OPENCLAW_WATCH_SESSION = watchSession;
// The watcher owns process restarts; keep SIGUSR1/config reloads in-process
// so inherited launchd/systemd markers do not make the child exit and stall.
childEnv.OPENCLAW_NO_RESPAWN = "1";
if (deps.args.length > 0) {
childEnv.OPENCLAW_WATCH_COMMAND = deps.args.join(" ");
}

View File

@@ -72,6 +72,19 @@ describe("restartGatewayProcessWithFreshPid", () => {
expect(spawnMock).not.toHaveBeenCalled();
});
it("keeps OPENCLAW_NO_RESPAWN ahead of inherited supervisor hints", () => {
clearSupervisorHints();
setPlatform("darwin");
process.env.OPENCLAW_NO_RESPAWN = "1";
process.env.LAUNCH_JOB_LABEL = "ai.openclaw.gateway";
const result = restartGatewayProcessWithFreshPid();
expect(result).toEqual({ mode: "disabled" });
expect(triggerOpenClawRestartMock).not.toHaveBeenCalled();
expect(spawnMock).not.toHaveBeenCalled();
});
it("returns supervised when launchd hints are present on macOS (no kickstart)", () => {
clearSupervisorHints();
expectLaunchdSupervisedWithoutKickstart({ launchJobLabel: "ai.openclaw.gateway" });

View File

@@ -76,6 +76,7 @@ describe("watch-node script", () => {
PATH: "/usr/bin",
OPENCLAW_WATCH_MODE: "1",
OPENCLAW_WATCH_SESSION: "1700000000000-4242",
OPENCLAW_NO_RESPAWN: "1",
OPENCLAW_WATCH_COMMAND: "gateway --force",
}),
}),
@@ -146,6 +147,38 @@ describe("watch-node script", () => {
expect(fakeProcess.listenerCount("SIGTERM")).toBe(0);
});
it("forces no-respawn for watch children even when supervisor hints are inherited", async () => {
const { child, spawn, watcher, createWatcher, fakeProcess } = createWatchHarness();
const runPromise = runWatchMain({
args: ["gateway", "--force"],
createWatcher,
env: {
LAUNCH_JOB_LABEL: "ai.openclaw.gateway",
PATH: "/usr/bin",
},
process: fakeProcess,
spawn,
});
expect(spawn).toHaveBeenCalledWith(
"/usr/local/bin/node",
["scripts/run-node.mjs", "gateway", "--force"],
expect.objectContaining({
env: expect.objectContaining({
LAUNCH_JOB_LABEL: "ai.openclaw.gateway",
OPENCLAW_NO_RESPAWN: "1",
}),
}),
);
fakeProcess.emit("SIGINT");
const exitCode = await runPromise;
expect(exitCode).toBe(130);
expect(child.kill).toHaveBeenCalledWith("SIGTERM");
expect(watcher.close).toHaveBeenCalledTimes(1);
});
it("ignores test-only changes and restarts on non-test source changes", async () => {
const childA = Object.assign(new EventEmitter(), {
kill: vi.fn(function () {