Bug: gateway restart spawns duplicate gateway processes on Windows (3 windows)
Summary
openclaw gateway restart on Windows produces 3 cmd windows instead of 1:
- The original gateway window (being shut down)
- A new window from the CLI's
schtasks /Run
- A third window from the gateway's self-restart script
The second and third race for the port — one succeeds, the other fails with "port already in use" and exits.
Root Cause
During openclaw gateway restart, two independent mechanisms both try to start a new gateway:
- CLI path (
daemon-cli.js): restartScheduledTask() does schtasks /End then schtasks /Run
- Self-restart path (
restart-C7ane9OU.js): The dying gateway process fires relaunchGatewayScheduledTask(), which spawns a detached .cmd script (openclaw-schtasks-restart-<uuid>.cmd) that retries schtasks /Run up to 12 times with 1s delay
The self-restart mechanism exists for commands.restart: true (config-change auto-restart), but it also fires during a CLI-initiated restart, causing a duplicate start.
Code trace
CLI restart (daemon-cli.js:19646):
async function restartScheduledTask({ stdout, env }) {
await execSchtasks(["/End", "/TN", taskName]);
// No delay
const res = await execSchtasks(["/Run", "/TN", taskName]); // ← Start #1
}
Self-restart on shutdown (restart-C7ane9OU.js:282):
function buildScheduledTaskRestartScript(taskName) {
// Writes a .cmd that retries schtasks /Run up to 12 times ← Start #2
}
function relaunchGatewayScheduledTask(env) {
spawn("cmd.exe", ["/d", "/s", "/c", scriptPath], {
detached: true,
stdio: "ignore",
windowsHide: true
}).unref();
}
Additionally, daemon-cli.js:22416 has a postRestartCheck that detects the old PID as "stale" (because includeUnknownListenersAsStale: process.platform === "win32") and can trigger a third service.restart() call.
Expected Behavior
openclaw gateway restart should produce exactly 1 new gateway window. Either:
- The CLI should signal "CLI-initiated restart" so the shutdown handler skips self-restart, OR
restartScheduledTask should not call schtasks /Run and rely on the self-restart script, OR
- Add a small delay between
/End and /Run and wait for the old PID to exit
Reproduction
openclaw gateway start
# Wait for gateway to be healthy
openclaw gateway restart
# Observe: 3 cmd windows appear (original + 2 new)
100% reproducible.
Workaround
Use openclaw gateway stop followed by openclaw gateway start instead of restart.
Environment
- OpenClaw v2026.3.8
- Windows 10 (build 26200)
- Node.js v24.14.0
commands.restart: true in config
- Gateway managed via "OpenClaw Gateway" scheduled task
Bug:
gateway restartspawns duplicate gateway processes on Windows (3 windows)Summary
openclaw gateway restarton Windows produces 3 cmd windows instead of 1:schtasks /RunThe second and third race for the port — one succeeds, the other fails with "port already in use" and exits.
Root Cause
During
openclaw gateway restart, two independent mechanisms both try to start a new gateway:daemon-cli.js):restartScheduledTask()doesschtasks /Endthenschtasks /Runrestart-C7ane9OU.js): The dying gateway process firesrelaunchGatewayScheduledTask(), which spawns a detached.cmdscript (openclaw-schtasks-restart-<uuid>.cmd) that retriesschtasks /Runup to 12 times with 1s delayThe self-restart mechanism exists for
commands.restart: true(config-change auto-restart), but it also fires during a CLI-initiated restart, causing a duplicate start.Code trace
CLI restart (
daemon-cli.js:19646):Self-restart on shutdown (
restart-C7ane9OU.js:282):Additionally,
daemon-cli.js:22416has apostRestartCheckthat detects the old PID as "stale" (becauseincludeUnknownListenersAsStale: process.platform === "win32") and can trigger a thirdservice.restart()call.Expected Behavior
openclaw gateway restartshould produce exactly 1 new gateway window. Either:restartScheduledTaskshould not callschtasks /Runand rely on the self-restart script, OR/Endand/Runand wait for the old PID to exitReproduction
100% reproducible.
Workaround
Use
openclaw gateway stopfollowed byopenclaw gateway startinstead ofrestart.Environment
commands.restart: truein config