fix(daemon): avoid workgroup schtasks user prompts

This commit is contained in:
Peter Steinberger
2026-05-27 06:17:07 +01:00
parent d92a33306e
commit f348284fa9
2 changed files with 40 additions and 3 deletions

View File

@@ -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("<UserId>alice</UserId>");
expect(captured?.xml).not.toContain("<GroupId>S-1-5-32-545</GroupId>");
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("<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>");
expect(upgradeXml).toContain(
"<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>",
);
expect(upgradeXml).toContain("<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>");
expectTaskRunCall(4);
});

View File

@@ -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<ReturnType<typeof execSchtasks>>;
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);