From 80a6eb3131f0962b4392fa5f1d1ed664dd074800 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 7 Mar 2026 19:56:47 +0000 Subject: [PATCH] fix(daemon): use locale-invariant schtasks running code detection (#39076) Co-authored-by: ademczuk --- CHANGELOG.md | 1 + src/daemon/schtasks.test.ts | 35 +++++++++++++++++++++++++++++++++++ src/daemon/schtasks.ts | 18 ++++++++++++++---- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cccd66fe0eb..2f23c4ee355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -268,6 +268,7 @@ Docs: https://docs.openclaw.ai - Gateway/chat delta ordering: flush buffered assistant deltas before emitting tool `start` events so pre-tool text is delivered to Control UI before tool cards, avoiding transient text/tool ordering artifacts in streaming. (#39128) Thanks @0xtangping. - Voice-call plugin schema parity: add missing manifest `configSchema` fields (`webhookSecurity`, `streaming.preStartTimeoutMs|maxPendingConnections|maxPendingConnectionsPerIp|maxConnections`, `staleCallReaperSeconds`) so gateway AJV validation accepts already-supported runtime config instead of failing with `additionalProperties` errors. (#38892) Thanks @giumex. - Agents/OpenAI WS reconnect retry accounting: avoid double retry scheduling when reconnect failures emit both `error` and `close`, so retry budgets track actual reconnect attempts instead of exhausting early. (#39133) Thanks @scoootscooob. +- Daemon/Windows schtasks runtime detection: use locale-invariant `Last Run Result` running codes (`0x41301`/`267009`) as the primary running signal so `openclaw node status` no longer misreports active tasks as stopped on non-English Windows locales. (#39076) Thanks @ademczuk. ## 2026.3.2 diff --git a/src/daemon/schtasks.test.ts b/src/daemon/schtasks.test.ts index 6eb4e23ffec..5aff10ea543 100644 --- a/src/daemon/schtasks.test.ts +++ b/src/daemon/schtasks.test.ts @@ -63,6 +63,41 @@ describe("scheduled task runtime derivation", () => { detail: "Task reports Running but Last Run Result=0x0; treating as stale runtime state.", }); }); + + it("detects running via result code when status is localized (German)", () => { + expect( + deriveScheduledTaskRuntimeStatus({ + status: "Wird ausgeführt", + lastRunResult: "0x41301", + }), + ).toEqual({ status: "running" }); + }); + + it("detects running via result code when status is localized (French)", () => { + expect( + deriveScheduledTaskRuntimeStatus({ + status: "En cours", + lastRunResult: "267009", + }), + ).toEqual({ status: "running" }); + }); + + it("treats localized status as stopped when result code is not a running code", () => { + expect( + deriveScheduledTaskRuntimeStatus({ + status: "Wird ausgeführt", + lastRunResult: "0x0", + }), + ).toEqual({ status: "stopped" }); + }); + + it("treats localized status without result code as stopped", () => { + expect( + deriveScheduledTaskRuntimeStatus({ + status: "Wird ausgeführt", + }), + ).toEqual({ status: "stopped" }); + }); }); describe("resolveTaskScriptPath", () => { diff --git a/src/daemon/schtasks.ts b/src/daemon/schtasks.ts index 091dad88b99..880e0430135 100644 --- a/src/daemon/schtasks.ts +++ b/src/daemon/schtasks.ts @@ -163,13 +163,23 @@ export function deriveScheduledTaskRuntimeStatus(parsed: ScheduledTaskInfo): { if (!statusRaw) { return { status: "unknown" }; } - if (statusRaw !== "running") { - return { status: "stopped" }; - } const normalizedResult = normalizeTaskResultCode(parsed.lastRunResult); const runningCodes = new Set(["0x41301"]); - if (normalizedResult && !runningCodes.has(normalizedResult)) { + const isRunningByCode = normalizedResult != null && runningCodes.has(normalizedResult); + const isRunningByStatus = statusRaw === "running"; + + // schtasks.exe localizes its Status field ("Running" in English, + // "Wird ausgeführt" in German, "En cours" in French, etc.). + // Prefer the locale-invariant Last Run Result code 0x41301 + // ("task is currently running") over string matching. (#39057) + if (!isRunningByStatus && !isRunningByCode) { + return { status: "stopped" }; + } + + // Cross-check: if the English status says "running" but the result + // code disagrees, the runtime state is likely stale. + if (isRunningByStatus && normalizedResult && !isRunningByCode) { return { status: "stopped", detail: `Task reports Running but Last Run Result=${parsed.lastRunResult}; treating as stale runtime state.`,