Skip to content

windows-keeper-unelevated

On an unelevated Windows box, the modern scheduled-task API says "access denied" where the 1990s one says "sure" — and a GUI-subsystem Python can't print, so a keeper that seems silent may just be mute.

Toolchain facts (each cost a debugging loop)

  • Register-ScheduledTask (CIM) fails unelevated with 0x80070005 for both S4U and Interactive logon types on this box. The legacy schtasks.exe /Create registers per-user tasks unelevated without complaint. The installer (scripts/install-keeper.ps1) uses schtasks.
  • PowerShell quoting into /TR is a trap: the first attempt put literal \" into the Task-To-Run, which the scheduler reported only as Last Result 0x80070002 (file not found). Space-free absolute paths need no quotes at all — the fix was deleting the quoting, not escaping harder.
  • pythonw.exe is the GUI subsystem: print is a no-op. Anything a scheduled keeper must say goes to a file (.dev/keeper.log), never stdout.
  • A scheduled task rots invisibly unless it proves liveness somewhere a human looks. The keeper touches .dev/keeper.heartbeat every tick; /healthz reports the heartbeat's age; the launcher footer renders it ("keeper: 3m ago"). A forgotten keeper now announces itself on the page the maintainer looks at daily.
  • Port sweeps must know their tenants: tailscaled legitimately listens on tailnet-ip:8123 to proxy the serve route — a naive "kill whatever is on my port" sweeps it. scripts/dev.py down matches only 0.0.0.0:PORT/127.0.0.1:PORT binds and skips tailscale-named processes.

The transferable lesson

Unattended infra earns trust only by being observable at the surface the human already watches. The heartbeat-in-the-footer pattern generalizes: any background healer should surface "when did I last run" inside the thing it heals, because a dead healer otherwise looks identical to a healthy one right up until the day it was needed.