Skip to content

Fix three Windows daemon-boot defects (silent serve() wedge, SIGHUP crash, elevation-requiring task XML) - #126

Open
Jukesygit wants to merge 3 commits into
CodeAbra:mainfrom
Jukesygit:fix/windows-daemon-boot
Open

Jukesygit wants to merge 3 commits into
CodeAbra:mainfrom
Jukesygit:fix/windows-daemon-boot

Conversation

@Jukesygit

Copy link
Copy Markdown

Windows install of 3.0.4 (manual path, per the README) produced a daemon that died or wedged on every boot. Three root causes, fixed in dependency order — found and verified live on Windows 11, Python 3.11, cp311-win_amd64 wheel.

1. signal.SIGHUP in the boot signal trace (instant death)

_install_boot_signal_trace iterates (signal.SIGTERM, signal.SIGINT, signal.SIGHUP). The tuple is built before the loop body runs, so the per-signal except AttributeError guard never fires — on Windows the daemon died at boot with:

AttributeError: module 'signal' has no attribute 'SIGHUP'

Fixed with the same getattr(signal, "SIGHUP", None) idiom the graceful-handler site in the same file already uses.

2. asyncio.start_unix_server probed above the IS_WINDOWS branch (silent wedge — the bad one)

SocketServer.serve ran

sig = inspect.signature(asyncio.start_unix_server)

before its if IS_WINDOWS: branch. asyncio exposes start_unix_server only on platforms with AF_UNIX, so on Windows this raised AttributeError before the loopback bind. Because serve() is scheduled fire-and-forget, the failure was silent: the daemon booted, warmed the embedder, ran boot-warmup (110 ms), and ticked its FSM — while serving nothing. .daemon.port was never written, every client reported "daemon not running", iai recall quietly degraded to the bank fallback, and the watchdog killed the process as reason=wedge when the 600 s cold-start grace expired. Every boot, indefinitely.

Fix: move the POSIX capability probe below the Windows return.

3. Task XML registers machine-wide, so schtasks /Create needs elevation

The rendered task XML has no <UserId> in its <LogonTrigger> or <Principal>. Task Scheduler reads that as "at logon of ANY user" — a machine-wide registration requiring elevation — so iai-mcp daemon install fails with ERROR: Access is denied from the normal unelevated shell. Fixed by adding a {USER_ID} placeholder to both elements, rendered as DOMAIN\user of the installing account. Registration now succeeds unelevated; the task still runs as the same interactive user at LeastPrivilege.

Tests

  • New tests/test_windows_daemon_boot_regressions.py covers all three. The Windows conditions are simulated (SIGHUP deleted, start_unix_server deleted + IS_WINDOWS forced, user id stubbed), so they run red on the old code and green on this branch on POSIX CI too.
  • tests/test_daemon_boot_signal_trace.py hardcoded the three-signal set and the 3+1 signal.signal call count; it now derives both from the platform — one more test that passes on Windows.

Verified on the Windows 11 host that hit this: with the three fixes the daemon binds in ~10 s, iai-mcp doctor goes 32/32, and recall serves semantic hits through the daemon.

tests/test_windows_daemon_boot_regressions.py  4 passed  (4 failed against unfixed src)
tests/test_daemon_boot_signal_trace.py         1 passed
tests/test_cli_daemon_install_windows.py       5 passed
tests/test_ipc_transport.py                    3 passed, 2 skipped

(tests/test_iai_recall_fail_fast.py has 2 failures on Windows both before and after this branch — pre-existing suite-port gap, untouched here.)

🤖 Generated with Claude Code

Jonas and others added 3 commits August 20, 2026 17:29
_install_boot_signal_trace named signal.SIGHUP inside a tuple, which is
built before the loop body runs -- so the per-signal AttributeError
guard never fired and the daemon died at boot on Windows, where SIGHUP
does not exist. Resolve it with getattr and filter, the same idiom the
graceful-handler site in this file already uses.

The existing breadcrumb test hardcoded the three-signal set; it now
derives the expectation from the platform, which also makes it pass on
Windows.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SocketServer.serve probed inspect.signature(asyncio.start_unix_server)
above its IS_WINDOWS branch. asyncio exposes start_unix_server only on
platforms with AF_UNIX, so on Windows the probe raised AttributeError
before the loopback path could bind. serve() runs as a fire-and-forget
task, so the failure was silent: the daemon booted, warmed the
embedder, and ticked its FSM while serving nothing; the port file was
never written, every client reported "daemon not running", recall
degraded to the bank fallback, and the watchdog killed the process as
wedged (reason=wedge) when the 600s cold-start grace expired -- on
every boot.

Move the POSIX capability probe below the Windows return.

Observed live on Windows 11 / Python 3.11 / iai-pme 3.0.4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The task XML carried no <UserId> in its LogonTrigger or Principal.
Task Scheduler reads that as "at logon of ANY user" -- a machine-wide
registration that requires elevation -- so schtasks /Create failed with
"Access is denied" from the normal unelevated shell daemon install
runs in.

Add a {USER_ID} placeholder to both elements, rendered as
DOMAIN\user of the installing account. Registration now succeeds
unelevated, and the task still runs as the same interactive user at
LeastPrivilege.

Also adds tests/test_windows_daemon_boot_regressions.py covering all
three Windows boot defects fixed in this branch; the regressions are
simulated, so the tests run on POSIX CI too.

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

Copy link
Copy Markdown
Owner

Thanks — the three root causes are real; I confirmed the SIGHUP one is still live on current main (the boot signal trace builds the (SIGTERM, SIGINT, SIGHUP) tuple before the loop, so the except AttributeError never fires on Windows). We just shipped 3.0.5, which reworked the daemon internals. GitHub still shows this mergeable, but since the surrounding code changed, could you rebase onto main and re-run your Windows 11 verification so we're sure the fix still lands? CI is also red right now. Planning to take this as the Windows daemon-boot fix once it's green.

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