Running Skrog unattended (CI runners, build agents)
On this page
Skrog’s supervisor keeps the engine alive across crashes, wsl --shutdown,
and sleep/resume — but it needs a logged-on interactive session to do it.
This page documents how to give a headless machine one. It is documentation on
purpose: Skrog will not set up auto-logon for you, because doing so means
writing an account password into the machine’s LSA secrets, and a tool that
does that silently on your behalf is a liability a security review would
rightly reject. You set it up, with eyes open, or you do not.
Why a session is required
WSL2 will not create its utility VM from session 0 (a Windows service). This is a WSL platform constraint, not a Skrog one — it binds Docker Desktop and Rancher Desktop identically:
LocalSystemis refused outright (WSL_E_LOCAL_SYSTEM_NOT_SUPPORTED).- A dedicated service account fails inside the Host Compute Service with
ERROR_LOGON_TYPE_NOT_GRANTEDeven holding Service + Batch + Interactive logon rights and local administrator.
So an unattended machine needs a real user session. In practice that means auto-logon: the machine boots, logs a chosen user in, and Skrog’s supervisor starts with that session (via the per-user autostart entry, below).
The session-0 findings above are marked provisional in the plan (Spike B, issue #3) pending a re-test on a clean VM. Until that lands, treat auto-logon as the supported path for unattended machines.
The playbook
1. Create a dedicated local account
Use a purpose-made low-privilege local account for the runner, not a personal or domain admin account. Auto-logon stores this account’s password on the machine; scope the blast radius accordingly.
# Elevated PowerShell
$pw = Read-Host -AsSecureString "Password for the runner account"
New-LocalUser -Name "skrog-runner" -Password $pw -PasswordNeverExpires
Add-LocalGroupMember -Group "Users" -Member "skrog-runner"
Local administrator is not required for Skrog itself (install runs non-admin). Add it only if your CI jobs need it.
2. Install Skrog as that user
Log in as skrog-runner once, interactively, and install. This registers the
per-user autostart entry so the supervisor starts at every logon:
skrog install --headless
skrog autostart status # should report enabled
skrog autostart uses the per-user Run key and launches the windowless
skrogw.exe, which starts skrog supervise — an interactive-session
process, which is exactly what WSL2 needs. (A scheduled task set to “run
whether logged on or not” performs a batch logon into session 0, where WSL
cannot start; that is why Skrog uses the Run key, not a task.)
skrogw.exe then stays resident as the supervisor’s watchdog. If the
supervisor dies — a crash, a stray Stop-Process, an out-of-memory kill — the
pipe would otherwise stay gone until someone logged in and ran skrog start,
which on an unattended runner can mean every job failing overnight. Instead it
comes back in about a second:
type %LOCALAPPDATA%\Skrog\watchdog.log
2026-09-09T22:15:23-05:00 supervisor exited 0xFFFFFFFF after 20s; restarting in 1s
The policy refuses to make things worse: a clean exit is final, a failure that
happens in milliseconds (a held single-instance lock, a bad flag, no install)
is not treated as a crash and does not loop, restarts back off from 1s to 30s,
and after 10 restarts in an hour the watchdog stops and says so. Whatever the
supervisor wrote to stderr — including the goroutine dump of a Go fatal error,
which is the only evidence such a crash leaves — is captured in
supervisor-stderr.log beside it.
Set SKROG_NO_WATCHDOG=1 in the runner’s environment to go back to
launch-and-forget.
If the supervisor keeps dying
Check watchdog.log for how often, then run skrog doctor and read the
injected modules check. Endpoint-security agents (EDR/DLP) load a DLL into
every process and rewrite function prologues to route through their own
trampolines; those trampolines assume a C thread stack, and Go’s goroutine
stacks — small, movable, their own calling convention — do not survive a hook
that restores the wrong frame. The result is a Go runtime fatal error
(unexpected return pc, found pointer to free object, an access violation at
an image-base-shaped address) with no Skrog frame at the top of the dump
and nothing wrong in the program that died.
Doctor names the module because the dump cannot. The actual fix is an
exclusion for skrog.exe and skrogw.exe from whoever manages the
agent — a policy change, not a code change. SKROG_NO_VSOCK=1 narrows the
window in the meantime, at the cost of the slower transport. See
#166.
3. Enable auto-logon
Use Sysinternals Autologon (recommended: it stores the password in an LSA secret rather than plain text in the registry):
autologon.exe skrog-runner <domain-or-machine> <password>
The registry alternative (DefaultUserName / DefaultPassword under
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon) stores the
password in clear text and should be avoided.
4. (Optional) Lock the session after logon
If the machine sits in the open, auto-logon then immediately lock keeps the desktop from being usable while the session — and therefore the engine — stays alive. Add a per-user startup entry:
%SystemRoot%\System32\rundll32.exe user32.dll,LockWorkStation
Locking does not stop the supervisor; the session remains logged on.
5. Verify
Reboot. With no interactive login, from an SSH session or a remote build step:
docker version # the engine answers
skrog status --json # "supervisor":"running","engine":"running"
skrog runner check # one verdict: auto-logon, autostart, supervisor, engine
skrog runner check verifies every step above without elevation and names the
missing one — including whether the auto-logon account is the one Skrog was
installed for, and whether the password sits in clear text in the registry
(step 3’s warning). It compares the account name but never prints it. --json
for fleet health scripts; skrog doctor includes the same check on any machine
where auto-logon is configured.
Security notes
- Auto-logon means the machine boots into a usable (or lockable) session with a stored credential. Treat the machine as holding that credential: restrict physical and RDP access, and prefer a dedicated account with only the rights the runner needs.
- Combine with an idle timeout (
skrog config set idle-timeout 30m) so an unattended machine reclaims the engine’s RAM between jobs; the nextdockercommand wakes it. - Nothing here is Skrog-specific plumbing — it is standard Windows unattended configuration. Skrog only asks for the session it documents here; it never creates it for you.