test(windows): isolate the daemon guards' rendezvous and stop hiding their failures - #2026
Merged
Merged
Conversation
…their failures Every Windows guard shared ONE daemon. test_daemon_stability.py said each section "runs under its OWN cache directory: daemon coordination is cache-scoped" -- it is not. The rendezvous key is FNV-1a over a compile-time domain string (service.c: cbm_daemon_rendezvous_key) under a machine-global parent (win_default_runtime_parent -> SHGetFolderPathW(CSIDL_LOCAL_APPDATA), which ignores the LOCALAPPDATA variable). So the guard's own `daemon stop` and kill -9 sections, the sibling guards, and anything else on the runner all met the same daemon, and a daemon wedged anywhere failed every check after it. That is the shape of this week's test-windows-guards red on #1874: lifecycle's `daemon start` connected, the UI-config handshake never answered, and stability / hook_augment / ui_drive / cli_non_ascii went red in a row with an orphan daemon killed at cleanup -- on a diff and a base that were each green elsewhere. CBM_RUNTIME_DIR is the only relocation hook (bootstrap.c, resolved in cbm_daemon_bootstrap_endpoint_new for every product endpoint). Each stability section now gets a private runtime parent next to its cache, created before the binary runs -- the endpoint validates the parent and never creates it -- and the daemon inherits it from the client that spawns it. Proven on macOS: a daemon on the default rendezvous keeps its pid across the whole guard (three stops and a kill -9 inside it). The reporting was hiding the cause as well: * every SETUP FAIL printed nothing at all, and every RED printed a 300-400 character HEAD slice, which is the allocator preamble, never the error. Excerpts now show the tail (1500 chars); setup paths print the daemon's full output. * a subprocess timeout raised on a parallel-client worker thread was lost and the client reported as "rc=none" with no output; it is now kept as a failed result with its partial output. A section-level timeout is a named RED with the command and output, not a bare traceback. * the MCP session's stderr went to DEVNULL; it now goes to a file that is printed when initialize never answers -- and that case is a RED against a running daemon, not a "setup" skip. * exit 2 was documented and never produced. Only `daemon start` failing raises it now (test_daemon_lifecycle.py owns that surface as a regression, so the board loses nothing); main() runs every section and names all of the failing ones. * test_hook_augment.py / test_cli_non_ascii_arg.py print full stdout AND stderr on their index-setup failures instead of 300 chars of stdout. On POSIX (local iteration only) the namespace lives under /tmp: sun_path caps the socket at 104 bytes on macOS and TMPDIR there is ~50 bytes deep. Out of scope, noted: test_daemon_lifecycle.py, test_non_ascii_path.py and test_ui_drive_listing.py still use the machine-global rendezvous; a too-long CBM_RUNTIME_DIR on POSIX is refused (ipc.c sun_path check) with no validation detail. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.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.
What
The Windows daemon guards shared one machine-global daemon, and their failure output hid why anything went red. This isolates
test_daemon_stability.pyper section viaCBM_RUNTIME_DIRand makes every failure path print what actually happened.Why — the false premise
test_daemon_stability.pydocumented that "each section runs under its OWN cache directory: daemon coordination is cache-scoped". It is not:service.c: cbm_daemon_rendezvous_key);win_default_runtime_parent()→SHGetFolderPathW(CSIDL_LOCAL_APPDATA), which ignores theLOCALAPPDATAvariable (so eventest_non_ascii_path.py's redirect does not relocate it);CBM_CACHE_DIRmoves the cache, never the rendezvous (bootstrap.csays exactly this).So the guard's own
daemon stop/kill -9sections, the sibling guards, and anything else on the runner met the same daemon, and a daemon wedged anywhere failed every check after it. That is the shape of this week'stest-windows-guardsred on #1874: lifecycle'sdaemon startconnected, the UI-config handshake never answered, then stability / hook_augment / ui_drive / cli_non_ascii went red in a row with an orphan daemon killed at cleanup — on a diff and a base that were each green elsewhere.CBM_RUNTIME_DIRis the only relocation hook (resolved incbm_daemon_bootstrap_endpoint_newfor every product endpoint). Each section now gets a private runtime parent next to its cache, created before the binary runs (the endpoint validates the parent and never creates it); the daemon inherits it from the client that spawns it.The reporting fixes
SETUP FAILprinted nothingREDprinted a 300–400 char head slice (= the allocator preamble)rc=none, no outputDEVNULLdaemon startfailing yields 2 (test_daemon_lifecycle.pyowns that surface as a regression, so the board loses nothing); every section runs and all failing ones are namedtest_hook_augment.py/test_cli_non_ascii_arg.py: 300 chars of stdout on setup failureVerification
win.sh guards— all guards GREEN, stability all 7 sections,guards rc=0.daemon stops and akill -9inside it.Out of scope, noted
test_daemon_lifecycle.py,test_non_ascii_path.py,test_ui_drive_listing.pystill use the machine-global rendezvous — follow-up.CBM_RUNTIME_DIRwhose socket path exceedssun_path(104 bytes on macOS) is refused at theipc.clength check with no validation detail — the same opacity family as Claude.ai Desktop App still cannot start mcp #1582/CBM_CACHE_DIR ignored; daemon still requires /private/tmp/cbm-daemon-<uid> on macOS #1621. The guard keeps its POSIX namespace under/tmpfor that reason.