mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 23:34:44 +00:00
fix(windows): prevent restart race from duplicate schtasks /Run (#52487)
Prevent duplicate scheduled-task /Run attempts during Windows gateway restart by checking the task state before retrying. Co-authored-by: Andy K <andyk-ms@users.noreply.github.com>
This commit is contained in:
@@ -320,6 +320,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Process tool: show input-wait hints from `log` and `poll` for idle interactive background sessions so operators can inspect stuck CLIs and resume them with existing input actions. Fixes #33957. Thanks @bitloi and @vincentkoc.
|
||||
- Shell env/Windows: hide the login-shell environment probe child window so gateway startup and shell-env refreshes do not flash a console on Windows. Fixes #78159. (#78266) Thanks @BradGroux.
|
||||
- MS Teams: surface blocked Bot Framework egress by logging JWKS fetch network failures and adding a Bot Connector send hint for transport-level reply failures. Fixes #77674. (#78081) Thanks @Beandon13.
|
||||
- Windows/restart: skip duplicate scheduled-task `/Run` calls when the gateway task is already running, using a locale-stable PowerShell task-state probe before retrying. Fixes #52044. (#52487) Thanks @andyk-ms.
|
||||
- Media/host-read: allow buffer-verified ZIP archives in the host-local media validator so agents can send ZIP attachments via the message tool. Fixes #78057. (#78292) Thanks @Linux2010.
|
||||
- Gateway/sessions: fast-path already-qualified model refs while building session-list rows so `openclaw sessions` and Control UI session lists avoid heavyweight model resolution on large stores. (#77902) Thanks @ragesaq.
|
||||
- Contributor PRs: remind external contributors to redact private information like IP addresses, API keys, phone numbers, and non-public endpoints from real behavior proof. Thanks @pashpashpash.
|
||||
|
||||
@@ -120,7 +120,13 @@ describe("relaunchGatewayScheduledTask", () => {
|
||||
expect(script).toContain(
|
||||
'openclaw restart attempt source=windows-task-handoff target="OpenClaw Gateway (work)"',
|
||||
);
|
||||
expect(script).toContain(
|
||||
`powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "(Get-ScheduledTask -TaskName 'OpenClaw Gateway (work)' -ErrorAction SilentlyContinue).State" 2>nul | findstr /I /C:"Running" >nul 2>&1`,
|
||||
);
|
||||
expect(script).toContain('schtasks /Run /TN "OpenClaw Gateway (work)" >>');
|
||||
expect(script.indexOf("powershell.exe -NoProfile")).toBeLessThan(
|
||||
script.indexOf('schtasks /Run /TN "OpenClaw Gateway (work)"'),
|
||||
);
|
||||
expect(script).toContain('del "%~f0" >nul 2>&1');
|
||||
});
|
||||
|
||||
@@ -140,6 +146,23 @@ describe("relaunchGatewayScheduledTask", () => {
|
||||
expect(script).toContain('schtasks /Run /TN "OpenClaw Gateway (custom)" >>');
|
||||
});
|
||||
|
||||
it("escapes custom task names in the PowerShell running-task probe", () => {
|
||||
spawnMock.mockImplementation((_file: string, args: string[]) => {
|
||||
createdScriptPaths.add(decodeCmdPathArg(args[3]));
|
||||
return { unref: vi.fn() };
|
||||
});
|
||||
|
||||
relaunchGatewayScheduledTask({
|
||||
OPENCLAW_WINDOWS_TASK_NAME: "OpenClaw Gateway (Bob's work)",
|
||||
});
|
||||
|
||||
const scriptPath = [...createdScriptPaths][0];
|
||||
const script = fs.readFileSync(scriptPath, "utf8");
|
||||
expect(script).toContain(
|
||||
"-Command \"(Get-ScheduledTask -TaskName 'OpenClaw Gateway (Bob''s work)' -ErrorAction SilentlyContinue).State\"",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns failed when the helper cannot be spawned", () => {
|
||||
spawnMock.mockImplementation(() => {
|
||||
throw new Error("spawn failed");
|
||||
|
||||
@@ -13,6 +13,10 @@ import { resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir.js";
|
||||
const TASK_RESTART_RETRY_LIMIT = 12;
|
||||
const TASK_RESTART_RETRY_DELAY_SEC = 1;
|
||||
|
||||
function quotePowerShellSingleQuotedLiteral(value: string): string {
|
||||
return `'${value.replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
function resolveWindowsTaskName(env: NodeJS.ProcessEnv): string {
|
||||
const override = env.OPENCLAW_WINDOWS_TASK_NAME?.trim();
|
||||
if (override) {
|
||||
@@ -29,6 +33,10 @@ function buildScheduledTaskRestartScript(params: {
|
||||
}): string {
|
||||
const { quotedLogPath, setupLines, taskName, taskScriptPath } = params;
|
||||
const quotedTaskName = quoteCmdScriptArg(taskName);
|
||||
const queryTaskStateCommand = `(Get-ScheduledTask -TaskName ${quotePowerShellSingleQuotedLiteral(
|
||||
taskName,
|
||||
)} -ErrorAction SilentlyContinue).State`;
|
||||
const quotedQueryTaskStateCommand = quoteCmdScriptArg(queryTaskStateCommand);
|
||||
const lines = [
|
||||
"@echo off",
|
||||
"setlocal",
|
||||
@@ -40,6 +48,9 @@ function buildScheduledTaskRestartScript(params: {
|
||||
":retry",
|
||||
`timeout /t ${TASK_RESTART_RETRY_DELAY_SEC} /nobreak >nul`,
|
||||
"set /a attempts+=1",
|
||||
// Avoid racing with another restart path that already started the scheduled task.
|
||||
`powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command ${quotedQueryTaskStateCommand} 2>nul | findstr /I /C:"Running" >nul 2>&1`,
|
||||
"if not errorlevel 1 goto cleanup",
|
||||
`schtasks /Run /TN ${quotedTaskName} >> ${quotedLogPath} 2>&1`,
|
||||
"if not errorlevel 1 goto cleanup",
|
||||
`if %attempts% GEQ ${TASK_RESTART_RETRY_LIMIT} goto fallback`,
|
||||
|
||||
Reference in New Issue
Block a user