Skip to content

Commit 0bf8218

Browse files
committed
fix(daemon): default Windows tasks to hidden launcher
Rebase PR #89380 onto current upstream/main and preserve cleanup of stale startup launcher variants.
1 parent 45056a4 commit 0bf8218

3 files changed

Lines changed: 53 additions & 19 deletions

File tree

src/daemon/schtasks.install.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,14 @@ describe("installScheduledTask", () => {
211211
});
212212
});
213213

214-
it("creates hidden launcher Windows tasks when requested", async () => {
214+
it("creates hidden launcher Windows tasks by default", async () => {
215215
await withUserProfileDir(async (_tmpDir, env) => {
216216
schtasksResponses.push(okSchtasksResponse, missingTaskResponse);
217217

218218
const { scriptPath } = await installDefaultGatewayTask({
219219
...env,
220220
USERDOMAIN: "WORKSTATION",
221221
USERNAME: "alice",
222-
OPENCLAW_WINDOWS_TASK_HIDDEN_LAUNCHER: "1",
223222
});
224223
const launcherPath = scriptPath.replace(/\.cmd$/i, ".vbs");
225224
const launcher = await fs.readFile(launcherPath, "utf8");
@@ -326,7 +325,7 @@ describe("installScheduledTask", () => {
326325
});
327326
});
328327

329-
it("updates existing tasks to use the hidden launcher when requested", async () => {
328+
it("updates existing tasks to use the hidden launcher by default", async () => {
330329
await withUserProfileDir(async (_tmpDir, env) => {
331330
// /Query, /Query /TN, /Change (TR-only), /Create /XML (upgrade re-apply), /Run.
332331
schtasksResponses.push(
@@ -341,7 +340,6 @@ describe("installScheduledTask", () => {
341340
...env,
342341
USERDOMAIN: "WORKSTATION",
343342
USERNAME: "alice",
344-
OPENCLAW_WINDOWS_TASK_HIDDEN_LAUNCHER: "true",
345343
});
346344
const launcherPath = scriptPath.replace(/\.cmd$/i, ".vbs");
347345

src/daemon/schtasks.startup-fallback.test.ts

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const {
7373
uninstallScheduledTask,
7474
} = await import("./schtasks.js");
7575

76-
function resolveStartupEntryPath(env: Record<string, string>, extension = "cmd") {
76+
function resolveStartupEntryPath(env: Record<string, string>, extension = "vbs") {
7777
const taskName = env.OPENCLAW_WINDOWS_TASK_NAME ?? "OpenClaw Gateway";
7878
return path.join(
7979
env.APPDATA,
@@ -86,8 +86,11 @@ function resolveStartupEntryPath(env: Record<string, string>, extension = "cmd")
8686
);
8787
}
8888

89-
async function writeStartupFallbackEntry(env: Record<string, string>) {
90-
const startupEntryPath = resolveStartupEntryPath(env);
89+
async function writeStartupFallbackEntry(
90+
env: Record<string, string>,
91+
extension: "cmd" | "vbs" = "vbs",
92+
) {
93+
const startupEntryPath = resolveStartupEntryPath(env, extension);
9194
await fs.mkdir(path.dirname(startupEntryPath), { recursive: true });
9295
await fs.writeFile(startupEntryPath, "@echo off\r\n", "utf8");
9396
return startupEntryPath;
@@ -292,24 +295,22 @@ describe("Windows startup fallback", () => {
292295
const startupEntryPath = resolveStartupEntryPath(env);
293296
const startupScript = await fs.readFile(startupEntryPath, "utf8");
294297
expect(result.scriptPath).toBe(resolveTaskScriptPath(env));
295-
expect(startupScript).toContain('start "" /min cmd.exe /d /c');
298+
expect(startupScript).toContain("WScript.Shell");
296299
expect(startupScript).toContain("gateway.cmd");
300+
expect(startupScript).toContain(`Run """${result.scriptPath}""", 0, False`);
297301
expectStartupFallbackSpawn();
298302
expect(childUnref).toHaveBeenCalled();
299303
expect(printed).toContain("Installed Windows login item");
300304
});
301305
});
302306

303-
it("uses a hidden Startup-folder launcher when requested", async () => {
307+
it("uses a hidden Startup-folder launcher by default", async () => {
304308
await withWindowsEnv("openclaw-win-startup-", async ({ env }) => {
305309
addStartupFallbackMissingResponses([
306310
{ code: 5, stdout: "", stderr: "ERROR: Access is denied." },
307311
]);
308312

309-
const result = await installGatewayScheduledTask({
310-
...env,
311-
OPENCLAW_WINDOWS_TASK_HIDDEN_LAUNCHER: "1",
312-
});
313+
const result = await installGatewayScheduledTask(env);
313314

314315
const startupEntryPath = resolveStartupEntryPath(env, "vbs");
315316
const startupScript = await fs.readFile(startupEntryPath, "utf8");
@@ -637,7 +638,7 @@ describe("Windows startup fallback", () => {
637638
it("keeps legacy Startup-folder cmd entries visible after hidden launcher opt-in", async () => {
638639
await withWindowsEnv("openclaw-win-startup-", async ({ env }) => {
639640
addStartupFallbackMissingResponses();
640-
await writeStartupFallbackEntry(env);
641+
await writeStartupFallbackEntry(env, "cmd");
641642

642643
await expect(
643644
isScheduledTaskInstalled({
@@ -650,10 +651,26 @@ describe("Windows startup fallback", () => {
650651
});
651652
});
652653

654+
it("keeps legacy Startup-folder vbs entries visible after hidden launcher opt-out", async () => {
655+
await withWindowsEnv("openclaw-win-startup-", async ({ env }) => {
656+
addStartupFallbackMissingResponses();
657+
await writeStartupFallbackEntry(env, "vbs");
658+
659+
await expect(
660+
isScheduledTaskInstalled({
661+
env: {
662+
...env,
663+
OPENCLAW_WINDOWS_TASK_HIDDEN_LAUNCHER: "0",
664+
},
665+
}),
666+
).resolves.toBe(true);
667+
});
668+
});
669+
653670
it("removes legacy Startup-folder cmd entries after hidden launcher opt-in", async () => {
654671
await withWindowsEnv("openclaw-win-startup-", async ({ env }) => {
655672
schtasksResponses.push({ code: 0, stdout: "", stderr: "" });
656-
const startupEntryPath = await writeStartupFallbackEntry(env);
673+
const startupEntryPath = await writeStartupFallbackEntry(env, "cmd");
657674
const stdout = new PassThrough();
658675

659676
await uninstallScheduledTask({
@@ -668,6 +685,24 @@ describe("Windows startup fallback", () => {
668685
});
669686
});
670687

688+
it("removes legacy Startup-folder vbs entries after hidden launcher opt-out", async () => {
689+
await withWindowsEnv("openclaw-win-startup-", async ({ env }) => {
690+
schtasksResponses.push({ code: 0, stdout: "", stderr: "" });
691+
const startupEntryPath = await writeStartupFallbackEntry(env, "vbs");
692+
const stdout = new PassThrough();
693+
694+
await uninstallScheduledTask({
695+
env: {
696+
...env,
697+
OPENCLAW_WINDOWS_TASK_HIDDEN_LAUNCHER: "0",
698+
},
699+
stdout,
700+
});
701+
702+
await expect(fs.access(startupEntryPath)).rejects.toThrow();
703+
});
704+
});
705+
671706
it("reports runtime from the gateway listener when using the Startup fallback", async () => {
672707
await withWindowsEnv("openclaw-win-startup-", async ({ env }) => {
673708
addStartupFallbackMissingResponses();

src/daemon/schtasks.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,10 @@ function resolveStartupEntryPath(env: GatewayServiceEnv, extension?: "cmd" | "vb
9696
function resolveStartupEntryPaths(env: GatewayServiceEnv): string[] {
9797
const primaryPath = resolveStartupEntryPath(env);
9898
const legacyCmdPath = resolveStartupEntryPath(env, "cmd");
99-
// Hidden VBS launchers supersede cmd launchers, but uninstall must remove the
100-
// legacy cmd path from older installs too.
101-
return uniqueStrings([primaryPath, legacyCmdPath]);
99+
const legacyVbsPath = resolveStartupEntryPath(env, "vbs");
100+
// Hidden VBS launchers supersede cmd launchers, but cleanup must remove both
101+
// launcher variants left by older installs or changed settings.
102+
return uniqueStrings([primaryPath, legacyCmdPath, legacyVbsPath]);
102103
}
103104

104105
// `/TR` is parsed by schtasks itself, while the generated `gateway.cmd` line is parsed by cmd.exe.
@@ -227,7 +228,7 @@ function resolveSchtasksCreateUser(env: GatewayServiceEnv, taskUser: string | nu
227228

228229
function shouldUseHiddenWindowsTaskLauncher(env: GatewayServiceEnv): boolean {
229230
const value = normalizeLowercaseStringOrEmpty(env.OPENCLAW_WINDOWS_TASK_HIDDEN_LAUNCHER);
230-
return value === "1" || value === "true" || value === "yes";
231+
return value !== "0" && value !== "false" && value !== "no";
231232
}
232233

233234
function resolveTaskLauncherScriptPath(env: GatewayServiceEnv, scriptPath: string): string {

0 commit comments

Comments
 (0)