Skip to content

fix(cli): run Windows sync without flashing a console window - #38

Open
sybrengg wants to merge 1 commit into
851-labs:mainfrom
sybrengg:fix/windows-hidden-sync-window
Open

fix(cli): run Windows sync without flashing a console window#38
sybrengg wants to merge 1 commit into
851-labs:mainfrom
sybrengg:fix/windows-hidden-sync-window

Conversation

@sybrengg

Copy link
Copy Markdown

Problem

On Windows the background sync opens a console window on every run — every 5 minutes. schtasks can only register tasks that run in the interactive session, and the task action points straight at service-sync.cmd (a console program), so a cmd.exe window flashes on the desktop each time the task fires. On a normal desktop it's a brief flash; for anyone in a full-screen app or game it repeatedly steals focus (in my case it kept knocking a full-screen game out of focus every 5 minutes).

Root cause

windowsTaskCreateArgs registers:

schtasks /Create /TN tokenmaxxing-sync /SC MINUTE /MO 5 /TR "…\service-sync.cmd" /F

schtasks has no flag for the "run whether the user is logged on or not" (S4U) logon type, nor any way to hide the window, so the launched console is always visible on the user's desktop.

Fix

Launch the existing wrapper through a tiny generated VBScript run by wscript.exe (a GUI-subsystem host) with window style 0:

' Auto-generated by tokenmaxxing. Runs the sync wrapper without a console window.
Dim shell, exitCode
Set shell = CreateObject("WScript.Shell")
exitCode = shell.Run("""…\service-sync.cmd""", 0, True)
WScript.Quit exitCode
/TR  ->  wscript.exe //nologo //b "…\service-sync-launcher.vbs"
  • No window ever appears. wscript.exe has no console, and Run(…, 0, …) hides the child. (Unlike powershell -WindowStyle Hidden, which still briefly flashes a console before hiding it.)
  • Exit codes preserved. bWaitOnReturn = True + WScript.Quit propagate the wrapper's exit code to Task Scheduler, so service status/doctor LastTaskResult stays accurate.
  • No elevation required. Keeps the current non-elevated service install UX. (An S4U / "run whether logged on or not" task would also hide the window, but requires admin at install time — a UX regression — so I avoided it.)
  • The launcher is written next to the wrapper on install/repair and removed on uninstall.
  • SERVICE_TEMPLATE_VERSION is bumped 5 → 6, so existing installs are seen as stale and self-migrate to the hidden launcher on their next scheduled repair.

The .cmd wrapper itself (log rotation, runner-pointer handling, logging) is unchanged — it's just no longer the task's entry point.

Testing

  • Added a renderWindowsLauncher unit test and updated the windowsTaskCreateArgs expectation.
  • apps/cli: tsc typecheck, oxlint --type-aware --type-check, and oxfmt all pass on the changed files.
  • Verified the mechanism end-to-end on Windows 11: wscript.exe //nologo //b launcher.vbs runs the wrapper with no visible window and returns the wrapper's exit code (a wrapper that exit /b 42 yields wscript exit code 42).

Heads up: a few pre-existing tests in service.test.ts fail when the suite is run on a Windows host because they assume POSIX path separators from node:path's join. Those are unrelated to this change (they fail the same way on main) and pass on the Linux/macOS CI. Happy to send a separate PR making them host-agnostic if that'd be useful.

🤖 Generated with Claude Code

The Windows service registers a schtasks task whose action points directly at
the service-sync.cmd wrapper. schtasks can only create tasks that run in the
interactive session, so a cmd console window appears on every run - every 5
minutes - which steals focus from full-screen apps and games.

Launch the wrapper through a tiny generated VBScript (service-sync-launcher.vbs)
via wscript.exe, a GUI-subsystem host, with window style 0. The sync then runs
with no window at all while still propagating the wrapper's exit code back to
Task Scheduler via WScript.Quit. Bump the service template version so existing
installs self-migrate to the hidden launcher on their next repair.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mynameistito

Copy link
Copy Markdown

Fixes #57

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants