Fix main's CI: a Windows-only path bug in my own gate, and folded_health's shared-registry flake - #517
Fix main's CI: a Windows-only path bug in my own gate, and folded_health's shared-registry flake#517emooreatx wants to merge 1 commit into
Conversation
…egistry flake
main's CI went red at the 0.5.196 merge after eleven consecutive green runs. Two
independent failures, one platform each, and PRs run ubuntu-only so neither could
have been caught before merging.
WINDOWS — MY GATE, BROKEN BY A PATH SEPARATOR. The scrape gate I added to prove
the node alias literal exists once filtered its own file with
`starts_with("src/key_convention.rs")` over a `display()` string. Windows renders
that `src\key_convention.rs`, so the filter excluded nothing and the gate failed
on the definition it exists to protect. It now keeps `PathBuf`s and filters with
`Path::ends_with`, which matches whole components and is separator-agnostic.
Mutation-verified after the fix: reintroducing a second literal still reds it.
UBUNTU — ONE LEAKED WARNING, FOUR FAILURES. `folded_health` shares a
process-global degradation registry. Three of the four failures assert
`degraded_mode == false`; the fourth indexes `warnings[0]`, which any node-tier
entry displaces. One leaked warning explains all four, and none of them is the
test that leaked it.
Three changes, because I could not reproduce the leak locally (10 clean runs
unloaded, 8 more pinned to two cores under competing load) and did not want to
ship a guess:
1. `tests/common/RaisedWarning` — an RAII guard that clears on drop, including
while unwinding. The old shape raised, then asserted, bound a loopback port
and made two HTTP round-trips, and only then cleared: anything in that window
that panicked leaked a `critical` into every later case. Correctness no longer
depends on the body of a test completing, which is the one thing a test cannot
promise. It also clears BEFORE raising, the defensive shape
`health_reports_degradation` and `retention_loop` already use and
`folded_health` did not — which is why a leak there was not self-healing.
2. The `warnings[0]` lookup finds its entry BY CODE. Index zero assumed the
brain's warning was first; a node-tier warning ahead of it silently retargeted
the assertion at a different entry.
3. The fixture guard now PRINTS the registry when it trips. The bare
`assert!(!baseline.1)` named nothing, so the cause had to be reconstructed
from four line numbers in a CI log. Only tests write to that registry, so its
contents are the diagnosis. Verified: a simulated leak now reports the exact
code and message that caused it.
I have not converted `health_reports_degradation` or `retention_loop` to the
guard. Both already clear defensively before raising, so a leak there
self-heals on the next case — the hazard is real but bounded, and churning two
passing files to fix a bug they do not have is not obviously right.
Local: 476 lib tests, all 15 gate binaries, and the three degradation-touching
suites green; clippy -D warnings and fmt clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gxnxsTWS8FkN3CT2JEz86
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0f69e374dd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let foreign: Vec<String> = hits | ||
| .iter() | ||
| .filter(|h| !h.starts_with("src/key_convention.rs")) | ||
| .filter(|p| !p.ends_with("key_convention.rs")) |
There was a problem hiding this comment.
Restrict the exclusion to the defining path
Path::ends_with("key_convention.rs") excludes every source file with that basename, not only src/key_convention.rs. If a nested module such as src/foo/key_convention.rs reintroduces the protected literal, this exact-once gate silently passes. Compare against the complete relative path using Path components so the check remains separator-independent without broadening the exclusion.
Useful? React with 👍 / 👎.
…not "foreign" on Windows (#540) main's `clippy + test (windows-latest)` has been red on every run since e46c45f (0.5.196, 2026-08-30) — the commit that added `key_convention::tests::the_node_alias_literal_exists_exactly_once_in_the_tree`. The test exempts its own file by `starts_with("src/key_convention.rs")`; on Windows `path.display()` yields `src\key_convention.rs`, so the file's own two sites read as foreign and the assertion fired: ``` Found: ["src\\key_convention.rs:118", "src\\key_convention.rs:2…"] ``` Ubuntu and macOS pass, and the PR lane is ubuntu-only, so nothing ever surfaced this before a merge. Same class as #517: a path bug in the gate itself. **Change:** the scanned path is normalised to forward slashes before the exemption is applied. Behaviour on Linux/macOS is unchanged (7/7 `key_convention` tests pass locally). Labelled `ci:full` so the Windows and macOS lanes run on this PR rather than after the merge. Because `cargo test` stops at the first failing binary, the Windows lane has not run the integration tests since 2026-08-30 — anything else Windows-only will surface on this run. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01QVZ3v83uX8noLxXr7d7pu9 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QVZ3v83uX8noLxXr7d7pu9
mainwent red at the 0.5.196 merge after eleven consecutive green runs. Two independent failures, one platform each.Windows — my gate, broken by a path separator
The scrape gate I added in 0.5.196 to prove the node alias literal exists exactly once filtered its own file with a
display()string prefix:So on Windows it excluded nothing and the gate failed on the very definition it exists to protect. It now keeps
PathBufs and filters withPath::ends_with, which matches whole components and is separator-agnostic. Mutation-verified after the fix: reintroducing a second literal innode_key.rsstill reds it.Ubuntu — one leaked warning, four failures
folded_healthshares a process-global degradation registry. Three of the four failures assertdegraded_mode == false; the fourth indexeswarnings[0], which any node-tier entry displaces. One leaked warning explains all four — and none of the four is the test that leaked it.I could not reproduce it locally: 10 clean runs unloaded, 8 more pinned to two cores under competing CPU load. So rather than ship a guess, three changes that each stand on their own:
1.
tests/common/RaisedWarning— an RAII guard that clears on drop, including while unwinding. The old shape raised, then asserted, bound a loopback port, and made two HTTP round-trips before clearing:Correctness no longer depends on the body of a test completing, which is the one thing a test cannot promise. The guard also clears before raising — the defensive shape
health_reports_degradationandretention_loopalready use andfolded_healthdid not, which is why a leak here was not self-healing.2. The
warnings[0]lookup finds its entry BY CODE. Index zero assumed the brain's warning was first; any node-tier warning ahead of it silently retargeted that assertion at a different entry.3. The fixture guard PRINTS the registry when it trips. The bare
assert!(!baseline.1, "fixture: this case needs an undegraded node")named nothing, so the cause had to be reconstructed from four line numbers in a CI log. Only tests write to that registry, so its contents are the diagnosis:Verified by simulating a leak. A recurrence now identifies its own cause.
What I deliberately did not do
health_reports_degradationandretention_loopkeep their manual raise/clear pairs. Both already clear defensively before raising, so a leak there self-heals on the next case — the hazard is real but bounded, and churning two passing files to fix a bug they do not have is not obviously right. Happy to convert them if you'd rather have one pattern.Honest limit
The RAII guard removes a real leak path, but CI showed no panicking test, so I cannot claim it is the cause of that specific run. Changes 2 and 3 are what make the next occurrence cheap: it will either not happen, or it will name itself.
Local
476 lib tests, all 15 gate binaries, and the three degradation-touching suites green.
clippy --all-targets -- -D warningsandfmt --checkclean.🤖 Generated with Claude Code
https://claude.ai/code/session_017gxnxsTWS8FkN3CT2JEz86