Windows: fix daemon liveness, crypto key corruption, and auth-token resilience#44
Open
danielhertz1999-bit wants to merge 6 commits into
Open
Conversation
Rebased onto upstream/main (which carries its own Windows port); this commit re-applies only the deltas upstream still lacks. Product fix: - crypto._try_file_set: os.open defaults to text mode on Windows, which translated 0x0A bytes in the raw AES key to 0x0D0A and silently corrupted it. Add os.O_BINARY (no-op on POSIX). Still unfixed upstream. - bench/contradiction_longitudinal: write CSV/JSON/MD as UTF-8 (cp1252 default crashed on the delta glyph). Test portability: - conftest: make Path.home() honor $HOME on Windows and mirror HOME->USERPROFILE so the home-redirecting fixtures (and their subprocesses) stay hermetic. - UTF-8 reads in source-scanning guards (constitutional_guards, bench_worktree, contradiction route columns). - Skip POSIX-only checks on Windows (archive 0o700, crypto geteuid/uid, launchd getuid); the product already guards these behind hasattr(os,geteuid). - cpu_features: match /proc/cpuinfo via as_posix() (Windows renders backslashes). - capture_hooks_install: assert .ps1/powershell hooks on Windows, .sh/bash on POSIX. - daemon_kill_mid_consolidation: SIGKILL->SIGTERM (TerminateProcess) fallback plus a bounded reopen-retry for Windows async byte-range lock release. - bridge_socket_first: skip on Windows (npm/mcp-wrapper build path is POSIX-only), matching test_bridge_no_spawn_path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
os.kill(pid, 0) is the POSIX "is this PID alive?" idiom, but on Windows
os.kill rejects signal 0 with OSError [WinError 87] (invalid parameter)
even for a live process. Three call sites treated that exception as
"process is dead", which made a healthy, actively-ticking daemon report
as down:
- doctor check (a) "daemon process alive" -> live daemon reported dead
- doctor check (m) heartbeat scanner -> scan aborted with WinError 87
- maintenance pre-flight daemon guard -> always concluded "no daemon
running" on Windows, which could let maintenance run against a store the
live daemon still holds
Each now uses psutil.pid_exists (correct and cross-platform; psutil is
already a hard dep), falling back to os.kill only on POSIX -- matching the
existing pattern in capture.py and lifecycle_lock._is_pid_alive. Verified:
doctor 4/25 -> 2/25 FAIL, checks (a) and (m) now pass and correctly
identify the live daemon PID. 149 relevant tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
uuid4() was unseeded, so near-tied scores at the top-K fidelity cutoff could flip the str(id) tie-break between runs, making test_centrality_for_runtime_approx_above_cutoff flaky independent of the approximation algorithm. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
On Windows the daemon authenticates clients with a token it generates at startup, writes to ~/.iai-mcp/.daemon.token, and holds in memory. If that file later disappears out-of-band (external cleanup, AV quarantine, a stale-file sweep) while the daemon keeps running, the TCP port stays up but EVERY new client fails the auth handshake — existing long-lived connections survive, so the daemon looks alive while new sessions and `daemon status` all report "daemon not running". The only recovery was a manual restart. The daemon still holds the token in memory, so it can restore the file cheaply. Add _ipc.reassert_token_if_missing() (idempotent; no-op on POSIX, in client processes, or when the file is present) and call it as the first step of the daemon tick. _generate_token now stashes the token in a module global (_CURRENT_TOKEN); _remove_token_file clears it so a deliberate shutdown/restart can't be resurrected. Observed in production on Windows 11: .daemon.token gone while PID was listening and serving old clients; new connect raised "Daemon auth token not found". Confirmed nothing in the daemon's own code removes the token without also removing the port file (which was intact), so the deletion was external — exactly the single-point-of-failure this heals. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
check (b) "socket file fresh" reported a missing-token connectivity failure
as "<...>.daemon.sock present but unreachable: FileNotFoundError" — doubly
misleading on Windows, where there is no .daemon.sock (the daemon listens on
TCP loopback) and the FileNotFoundError actually meant the auth token file
was gone, not the socket.
Report the real endpoint (127.0.0.1:<port>) instead of the nonexistent sock
path, and when the token file is missing while the port is listening, say so
explicitly with the remediation ("restart the daemon to regenerate it").
_socket_connect_probe now surfaces the underlying FileNotFoundError message
so a missing port vs a missing token are distinguishable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Node MCP wrapper's PythonCoreBridge never sent the Windows TCP auth token before writing RPC requests, so the daemon's _make_authenticated_handler (added same-day in 21fb055) silently closed every connection. Add authenticateConnection() to ipc.ts and call it at all three of bridge.ts's connection sites. Also add episodes-recent/curiosity-pending/events-query/schema-list CLI subcommands with the same daemon-first/direct-store-fallback shape as the existing bank-recall command, reusing the daemon's own dispatch functions (moved into core/_query_dispatch.py) so the two paths can't drift apart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
A set of Windows daemon fixes discovered while running the memory engine on
Windows 11. Each is a POSIX-idiom-on-Windows bug that misbehaves silently.
Branch is
main+ these commits only.1.
os.kill(pid, 0)liveness probes fail on Windows (WinError 87)os.kill(pid, 0)is the POSIX "is this PID alive?" idiom, but on Windowsos.killrejects signal0withOSError [WinError 87]even for a liveprocess. Three call sites treated that as "process dead", making a healthy,
ticking daemon report as down:
doctorcheck (a) "daemon process alive" → live daemon reported deaddoctorcheck (m) heartbeat scanner → aborted with WinError 87Windows, which could let maintenance run against a store the live daemon holds
Each now uses
psutil.pid_exists(cross-platform; psutil is already a harddep), falling back to
os.killonly on POSIX — matching the existing patternin
capture.pyandlifecycle_lock._is_pid_alive.2. Auth-token file is a single point of failure with no self-heal
On Windows the daemon authenticates clients with a token it writes to
~/.iai-mcp/.daemon.tokenat startup and holds in memory. If that file latervanishes out-of-band (external cleanup, AV, a stale-file sweep) while the daemon
runs, the TCP port stays up but every new client fails the auth handshake —
old connections survive, so the daemon looks alive while new sessions and
daemon statusall report "not running". Observed in production.Adds
_ipc.reassert_token_if_missing()(idempotent; no-op on POSIX / in clientprocesses / when the file exists), called as the first daemon tick step, so a
vanished token self-heals within one tick instead of needing a manual restart.
doctorcheck (b) also now reports this precisely — the real endpoint(
127.0.0.1:<port>) and "auth token file missing (restart to regenerate)"instead of the old misleading "
.daemon.sockpresent but unreachable".3. Crypto AES key corruption on Windows (missing
O_BINARY)crypto._try_file_setusedos.openwithoutos.O_BINARY; on Windows thatdefaults to text mode and translated
0x0Abytes in the raw AES key to0x0D0A, silently corrupting it. Addsos.O_BINARY(no-op on POSIX).Tests
Test-portability changes so the suite runs green on Windows, plus a determinism
fix for a pre-existing centrality-bench flake (unseeded
uuid4()let near-tiedtop-K scores flip a
str(id)tie-break between runs). New unit tests cover thetoken self-heal. No behavior change on POSIX.
🤖 Generated with Claude Code