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 os from "node:os";
import path from "node:path"; import path from "node:path";
import { describe, expect, it } from "vitest"; 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", () => { describe("schtasks runtime parsing", () => {
it.each(["Ready", "Running"])("parses %s status", (status) => { 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", () => { describe("resolveTaskScriptPath", () => {
it.each([ it.each([
{ {

View File

@@ -132,6 +132,53 @@ export function parseSchtasksQuery(output: string): ScheduledTaskInfo {
return info; 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({ function buildTaskScript({
description, description,
programArguments, programArguments,
@@ -307,12 +354,12 @@ export async function readScheduledTaskRuntime(
}; };
} }
const parsed = parseSchtasksQuery(res.stdout || ""); const parsed = parseSchtasksQuery(res.stdout || "");
const statusRaw = parsed.status?.toLowerCase(); const derived = deriveScheduledTaskRuntimeStatus(parsed);
const status = statusRaw === "running" ? "running" : statusRaw ? "stopped" : "unknown";
return { return {
status, status: derived.status,
state: parsed.status, state: parsed.status,
lastRunTime: parsed.lastRunTime, lastRunTime: parsed.lastRunTime,
lastRunResult: parsed.lastRunResult, lastRunResult: parsed.lastRunResult,
...(derived.detail ? { detail: derived.detail } : {}),
}; };
} }