fix(gateway): avoid stale running status from Windows Scheduled Task (openclaw#19504) thanks @Fologan

Verified:
- pnpm vitest src/daemon/schtasks.test.ts
- pnpm check
- pnpm build

Co-authored-by: Fologan <164580328+Fologan@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Fologan
2026-03-02 08:12:24 -06:00
committed by GitHub
parent f2468feb86
commit 8421b2e848
2 changed files with 96 additions and 4 deletions

View File

@@ -2,7 +2,12 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { parseSchtasksQuery, readScheduledTaskCommand, resolveTaskScriptPath } from "./schtasks.js";
import {
deriveScheduledTaskRuntimeStatus,
parseSchtasksQuery,
readScheduledTaskCommand,
resolveTaskScriptPath,
} from "./schtasks.js";
describe("schtasks runtime parsing", () => {
it.each(["Ready", "Running"])("parses %s status", (status) => {
@@ -20,6 +25,46 @@ describe("schtasks runtime parsing", () => {
});
});
describe("scheduled task runtime derivation", () => {
it("treats Running + 0x41301 as running", () => {
expect(
deriveScheduledTaskRuntimeStatus({
status: "Running",
lastRunResult: "0x41301",
}),
).toEqual({ status: "running" });
});
it("treats Running + decimal 267009 as running", () => {
expect(
deriveScheduledTaskRuntimeStatus({
status: "Running",
lastRunResult: "267009",
}),
).toEqual({ status: "running" });
});
it("treats Running without last result as running", () => {
expect(
deriveScheduledTaskRuntimeStatus({
status: "Running",
}),
).toEqual({ status: "running" });
});
it("downgrades stale Running status when last result is not a running code", () => {
expect(
deriveScheduledTaskRuntimeStatus({
status: "Running",
lastRunResult: "0x0",
}),
).toEqual({
status: "stopped",
detail: "Task reports Running but Last Run Result=0x0; treating as stale runtime state.",
});
});
});
describe("resolveTaskScriptPath", () => {
it.each([
{

View File

@@ -132,6 +132,53 @@ export function parseSchtasksQuery(output: string): ScheduledTaskInfo {
return info;
}
function normalizeTaskResultCode(value?: string): string | null {
if (!value) {
return null;
}
const raw = value.trim().toLowerCase();
if (!raw) {
return null;
}
if (/^0x[0-9a-f]+$/.test(raw)) {
return `0x${raw.slice(2).replace(/^0+/, "") || "0"}`;
}
if (/^\d+$/.test(raw)) {
const numeric = Number.parseInt(raw, 10);
if (Number.isFinite(numeric)) {
return `0x${numeric.toString(16)}`;
}
}
return raw;
}
export function deriveScheduledTaskRuntimeStatus(parsed: ScheduledTaskInfo): {
status: GatewayServiceRuntime["status"];
detail?: string;
} {
const statusRaw = parsed.status?.trim().toLowerCase();
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)) {
return {
status: "stopped",
detail: `Task reports Running but Last Run Result=${parsed.lastRunResult}; treating as stale runtime state.`,
};
}
return { status: "running" };
}
function buildTaskScript({
description,
programArguments,
@@ -307,12 +354,12 @@ export async function readScheduledTaskRuntime(
};
}
const parsed = parseSchtasksQuery(res.stdout || "");
const statusRaw = parsed.status?.toLowerCase();
const status = statusRaw === "running" ? "running" : statusRaw ? "stopped" : "unknown";
const derived = deriveScheduledTaskRuntimeStatus(parsed);
return {
status,
status: derived.status,
state: parsed.status,
lastRunTime: parsed.lastRunTime,
lastRunResult: parsed.lastRunResult,
...(derived.detail ? { detail: derived.detail } : {}),
};
}