Conversation
_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>
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_amd64wheel.1.
signal.SIGHUPin the boot signal trace (instant death)_install_boot_signal_traceiterates(signal.SIGTERM, signal.SIGINT, signal.SIGHUP). The tuple is built before the loop body runs, so the per-signalexcept AttributeErrorguard never fires — on Windows the daemon died at boot with:Fixed with the same
getattr(signal, "SIGHUP", None)idiom the graceful-handler site in the same file already uses.2.
asyncio.start_unix_serverprobed above theIS_WINDOWSbranch (silent wedge — the bad one)SocketServer.serveranbefore its
if IS_WINDOWS:branch. asyncio exposesstart_unix_serveronly on platforms with AF_UNIX, so on Windows this raisedAttributeErrorbefore the loopback bind. Becauseserve()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.portwas never written, every client reported "daemon not running",iai recallquietly degraded to the bank fallback, and the watchdog killed the process asreason=wedgewhen 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 /Createneeds elevationThe 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 — soiai-mcp daemon installfails withERROR: Access is deniedfrom the normal unelevated shell. Fixed by adding a{USER_ID}placeholder to both elements, rendered asDOMAIN\userof the installing account. Registration now succeeds unelevated; the task still runs as the same interactive user atLeastPrivilege.Tests
tests/test_windows_daemon_boot_regressions.pycovers all three. The Windows conditions are simulated (SIGHUP deleted,start_unix_serverdeleted +IS_WINDOWSforced, 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.pyhardcoded the three-signal set and the 3+1signal.signalcall 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 doctorgoes 32/32, and recall serves semantic hits through the daemon.(
tests/test_iai_recall_fail_fast.pyhas 2 failures on Windows both before and after this branch — pre-existing suite-port gap, untouched here.)🤖 Generated with Claude Code