From f348284fa9ebfd3eb0173bb4e52e4ed08f844f60 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 27 May 2026 06:17:07 +0100 Subject: [PATCH] fix(daemon): avoid workgroup schtasks user prompts --- src/daemon/schtasks.install.test.ts | 25 ++++++++++++++++++++++++- src/daemon/schtasks.ts | 18 ++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/daemon/schtasks.install.test.ts b/src/daemon/schtasks.install.test.ts index 59e9cc8305a..b8a83c7f304 100644 --- a/src/daemon/schtasks.install.test.ts +++ b/src/daemon/schtasks.install.test.ts @@ -271,6 +271,27 @@ describe("installScheduledTask", () => { }); }); + it("omits /RU for workgroup accounts so schtasks can use the current local user", async () => { + await withUserProfileDir(async (_tmpDir, env) => { + schtasksResponses.push(okSchtasksResponse, missingTaskResponse); + + await installDefaultGatewayTask({ + ...env, + USERDOMAIN: "WORKGROUP", + USERNAME: "alice", + }); + + expectInitialTaskQueries(); + const createCall = schtasksCalls[2]; + expect(createCall?.slice(0, 5)).toEqual(["/Create", "/F", "/TN", "OpenClaw Gateway", "/XML"]); + expect(createCall).not.toContain("/RU"); + const captured = xmlPayloadCaptures.find((entry) => entry.index === 2); + expect(captured?.xml).toContain("alice"); + expect(captured?.xml).not.toContain("S-1-5-32-545"); + expectTaskRunCall(3); + }); + }); + it("re-applies the XML on /Change so upgraded tasks adopt battery flags (#59299)", async () => { await withUserProfileDir(async (_tmpDir, env) => { // /Query yes, /Query /TN yes, /Change ok, /Create /XML ok (upgrade), /Run ok. @@ -296,7 +317,9 @@ describe("installScheduledTask", () => { const upgradeCapture = xmlPayloadCaptures.find((entry) => entry.index === 3); expect(upgradeCapture).toBeDefined(); const upgradeXml = upgradeCapture?.xml ?? ""; - expect(upgradeXml).toContain("false"); + expect(upgradeXml).toContain( + "false", + ); expect(upgradeXml).toContain("false"); expectTaskRunCall(4); }); diff --git a/src/daemon/schtasks.ts b/src/daemon/schtasks.ts index c65d7cf4933..ebf3d6c41ab 100644 --- a/src/daemon/schtasks.ts +++ b/src/daemon/schtasks.ts @@ -200,12 +200,25 @@ function resolveTaskUser(env: GatewayServiceEnv): string | null { return username; } const domain = env.USERDOMAIN; + if (normalizeLowercaseStringOrEmpty(domain) === "workgroup") { + return username; + } if (domain) { return `${domain}\\${username}`; } return username; } +function resolveSchtasksCreateUser(env: GatewayServiceEnv, taskUser: string | null): string | null { + // Workgroup hosts can report USERDOMAIN=WORKGROUP even though schtasks wants + // the current local account. Keep the XML user-scoped, but omit /RU so + // Task Scheduler binds the task to the caller instead of prompting. + if (normalizeLowercaseStringOrEmpty(env.USERDOMAIN) === "workgroup") { + return null; + } + return taskUser; +} + function shouldUseHiddenWindowsTaskLauncher(env: GatewayServiceEnv): boolean { const value = normalizeLowercaseStringOrEmpty(env.OPENCLAW_WINDOWS_TASK_HIDDEN_LAUNCHER); return value === "1" || value === "true" || value === "yes"; @@ -1170,9 +1183,10 @@ async function activateScheduledTask(params: { let create: Awaited>; try { const xmlArgs = ["/Create", "/F", "/TN", taskName, "/XML", xmlPath]; - const xmlArgsWithUser = taskUser ? [...xmlArgs, "/RU", taskUser, "/NP"] : xmlArgs; + const createUser = resolveSchtasksCreateUser(params.env, taskUser); + const xmlArgsWithUser = createUser ? [...xmlArgs, "/RU", createUser, "/NP"] : xmlArgs; create = await execSchtasks(xmlArgsWithUser); - if (create.code !== 0 && taskUser) { + if (create.code !== 0 && createUser) { // Retry without the elevated `/RU` form, matching the pre-XML behavior // for accounts whose service password cannot be stored. create = await execSchtasks(xmlArgs);