You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
MeshNodeLanguageServiceTest teardown DIRTY — one pooled I/O leaf still ignores cancellation after #2598 (#2665 names the pool on the next occurrence) #2616
Attribution: main's last two completed Build-and-Test runs (89f8862, aeecb0d) pass shard 2 with zero hits on this test, so it is not deterministic on main and not #2587's regression. Rerun requested on #2587 for that reason alone.
Why it is not just "another flake": it is a NEW face, and #2598 (merged today, "Pooled I/O leaf now observes cancellation: script-completion…") changed exactly MeshNodeLanguageService.cs, KernelScriptReferences.cs (GetReferences → GetReferencesAsync(…, ct)), KernelExecutor.cs, MeshScriptEnvironment.cs. A first-in-process reference build that now observes cancellation is precisely the kind of change that can turn a completion request into a race under shard load: if the first completion's reference build is cancelled/torn down and the second request sees a partially built reference set, "filter by typed prefix" could see a truncated symbol list. Per the house rule this is a race to pin, not a flake to retry — start with #2598's diff and the test's expectation of a complete reference set on first use.
Filed by Claude Code during the 2026-08-28 deploy tail.
Fresh measurement — only one occurrence so far; too soon to call it either way
Swept every MeshWeaver Build and Test run on main, 2026-08-26T07:38Z → 2026-08-28T20:46Z (61 h,
121 non-cancelled attempts, 19 failures, each cross-checked via the shard job's own Test failed/crash annotations).
MeshNodeLanguageServiceTest.ScriptCompletions_FilterByTypedPrefix_NotJustTheAlphabet appears exactly once: run 33171489378,
2026-08-28T12:32:28Z, shard 2 — the same event this issue was filed from. Since then: 18 valid main attempts, 0 recurrences (through 2026-08-28T20:46Z; no further main push has run as of
this comment, 2026-08-29T06:14Z). Since the issue was filed (14:18Z): 11 valid attempts, 0
recurrences.
That is well short of a confident window — the rule of thumb here is ~30 clean runs before calling
something not-reproducing, and no fix has landed since (#2598/102275d1e is still the most recent
commit touching MeshNodeLanguageService.cs / KernelScriptReferences.cs). One occurrence is not
enough to confirm the race is real and recurring either, versus a one-off under whatever load shard
2 was carrying at that moment.
Leaving open — could not establish a rate in either direction with this little data. Worth
re-checking once more main runs accumulate.
All four assertions (NotBeEmpty, Count ≤ 15, Contain "Mesh", OnlyContain "Mes") were green in 1.04 s. The reported exception is an InvalidOperationException from MonolithMeshTestBase.DisposeAsync() (MonolithMeshTestBase.cs:1574) — a teardown failure wearing the test's name. Nothing about the reference set was ever observed to be wrong. The "truncated symbol list" hypothesis is refuted at the observation level.
2. It predates #2598 by ~4 hours — it is a recurrence of #2578
#2598 merged at 11:34Z. #2578 is the observation that motivated #2598 — same test, same exception text, same 30 s, same anonymous 1 pooled I/O leaf(s). So "first seen right after #2598" is inverted: it predates that PR and recurred unchanged after it. This should be treated as a recurrence of #2578.
3. The Lazy<Task<T>> cancellation hypothesis is refuted at the code level too
Both memos are exactly the shape worth suspecting — KernelScriptReferences.cs:64 and MeshNodeLanguageService.cs:68 — but the "cancelled task cached forever" classic does not apply: Task.WaitAsync(ct) returns a new task that faults with OCE and never touches the awaited task, and neither memo is started with a caller token (Task.Run(CreateSharedSnapshot) takes none; BuildScriptWorkspaceAsync awaits with explicit CancellationToken.None). A cancelled caller cannot poison either memo.
🚨 4. Why this has now cost two investigations: #2598's own diagnostic is inert
#2480 asked for the offending pool to be named. #2598 added it — IoPoolRegistry.cs:149, "IoPoolDrain: pool '{PoolName}' did not finish {Residual} leaf(es)…". On the first recurrence it produced nothing: zero hits in the job log, the trx, and collected-logs/.
Cause: it goes to ILogger, and DrainAll() runs afterMesh.Dispose(). MEASURED: of 294 Mesh.Dispose() invoking windows in this shard's trx, ZERO contain an ILogger line at any level — while the same trx carries 84 [Warning] [SynchronizationStream] and 76 [Warning] [RoutingServiceBase] records from before dispose. The mesh's log sink stops capturing at dispose, so the one diagnostic written to name the pool is structurally invisible in exactly the window it exists for.
PR #2665 fixes that: DrainAll returns the per-pool residuals, and both callers route them somewhere that survives dispose (TestPhaseTrace → the file CI keeps, which also survives an exit=124 host kill; TeardownReport.ResidualByPool in production). The trace becomes leakedIoLeaves=1 pools=[Query=1]. That matters because Query=1 and Compile=1 are different bugs, and IoPool.Drain yields a residual from three distinct causes that all currently reduce to the same integer.
5. What the evidence says the leaf probably is (NOT proven — needs the name)
30045 ms is exactly one DrainTimeout (IoPool.cs:80).
Strongest un-eliminated candidate on this exact path: MeshNodeLanguageService.cs:153 calls usageIndex.EnsureFresh(), and CompletionUsageIndex.EnsureFresh() (:79) fires a fire-and-forget, never-disposed, unownedmesh.Query<MeshNode>(...) subscription with .Timeout(QueryTimeout) where QueryTimeout = 30 s (:43), subscribed through the Query pool. A second independent 30 s budget coinciding with the drain's is the expected shape. CompletionMemoryStore has the same unowned .Subscribe(...) at :102, :182, :265.
If #2665 lands and the next occurrence says pools=[Query=1], that candidate is confirmed and the fix is to give EnsureFresh() an owner (hold the IDisposable, dispose the prior one, dispose on teardown) rather than leaving a 30 s subscription running past the request that started it.
6. One thing #2598 made worse, worth a look regardless
Task.Run(CreateSharedSnapshot) and the CancellationToken.None continuation now run on bare ThreadPool items that no IIoPool tracks and DrainAll() cannot see or join, while capturing hub.ServiceProvider. The PR moved the largest Roslyn workload in the process out of the drain's sight rather than making it drainable. Evidence it runs past TEST_END: DISPOSE_MEM rss Δ+161 MiB on this 1-second test, versus Δ+9 / Δ+27 MiB on the same class's other instances.
Recommendation: retitle/close this as a duplicate of #2578, land #2665 so the third occurrence names its pool, and do not re-run, widen DrainTimeout (the guard's own message forbids it), or skip-list the test — the test is passing; teardown is not.
changed the title [-]Flake: MeshNodeLanguageServiceTest.ScriptCompletions_FilterByTypedPrefix_NotJustTheAlphabet on shard 2 — first seen right after #2598 touched MeshNodeLanguageService[/-][+]MeshNodeLanguageServiceTest teardown DIRTY — one pooled I/O leaf still ignores cancellation after #2598 (#2665 names the pool on the next occurrence)[/+]on Aug 30, 2026
Second face, pool NAMED (#2665 delivered): DocumentBlocksAreaRenderTest leaks one Layout-pool leaf
Observed 2026-08-30 on #2733 (run 33300432494, shard 4, head 4d7812de6). The PR moves the modules/ generations GC off the readiness path (MeshWeaver.PluginCatalog + Memex.Portal.Shared) — MeshWeaver.ContentCollections.Indexing.Graph.Test has no project-reference path to either assembly (checked every csproj in its closure; MeshWeaver.Graph.csproj mentions Memex.Portal.Shared only in a comment), so the diff is not loaded in the failing host. Main was green at d3272b5f8 (07:28) and a0d404b89 (07:55).
The test PASSED; teardown found the leak — the #2616 shape exactly, on a different test, and this time the pool is named (_meshweaver-test-trace.log, testResults-shard4):
So the leaf that will not cancel is a IoPoolNames.LayoutSubscribeThroughPool leaf — the layout-area render subscribe (RenderBlocksUntil → LayoutAreaReference over the Document blocks area), not a script-completion / MeshNodeLanguageService leaf. That refutes the remaining "#2598 started this" thread here: the common factor is the render-subscribe leaf ignoring the pool's cancellation at drain, and MeshNodeLanguageServiceTest is just one of the tests that renders.
Also in the stdout for this test (before the DIRTY teardown): three ROUTER_TRAFFIC errors — RawJson with the mesh hub as sender and DataChangedEvent/SubscribeAck with the mesh hub as target, for TestData/content/_Documents/vertrag_2025.txt — i.e. the render's data-change subscription is wired through the mesh (router) hub rather than a hub of its own, which is the likeliest place a subscribe leaf sits outside the drain's cancellation.
Not re-run; the PR stays red until this is fixed or a fresh run happens to miss the race.
The test itself PASSES.=== TEST END === is logged with no assertion failure; only the class DisposeAsync fails, 30 s later. Whatever the leaf is, it is not the completion assertion.
20 local runs, zero reproductions, split evenly across the PR branch and main (be39fc607) — single test ×5 each, and the whole class under DOTNET_PROCESSOR_COUNT=4 ×5 each. It does not reproduce on this Mac even under the repo's usual CI-race lever, which reinforces that the trigger is shard-level load rather than anything in the class.
Still unnamed
Consistent with the note in the title, this occurrence still does not name the pool. The only IoPool stack in the shard log belongs to MeshWeaver.Hosting.Test's FileSystemStorageAdapter.WriteAtomic diagnostic — a different, passing project — so it is not evidence about this leaf. Whatever #2665 was expected to surface did not appear here either.
Cost recorded: this is now a second PR blocked by this flake — a security fix (a live authorization bypass) held out of main behind it, in a window where merge slots are being rationed because CD is starving.
Same signature: the test passes (=== TEST END … ScriptCompletions_FilterByTypedPrefix_NotJustTheAlphabet === at 12:48:27.592, no assertion), Mesh.Dispose() runs the full 30 s drain, DisposeAsync throws teardown DIRTY — 1 pooled I/O leaf(s) still running.
#2665's naming did fire — in the trace artifact, not the job's stdout, which is why the previous occurrence read it as absent. testResults-shard1/collected-logs/_meshweaver-test-trace.log:
So the leaf that outlives the drain is on the Query pool — a MeshQuery / IMeshService.Query leaf, not the script-completion/reference build #2598 touched (that runs on the compile lane). teardown-stragglers.log captured nine first-chance exceptions in this shard, at 12:46:46 and 12:50:44–50 — none inside the 12:48:27–12:48:57 window, so the Query leaf never threw: it is parked, not faulting. The MeshQuery.MergeProviderObservables frames that do appear in the other captures ride Observable.FromAsync/StartAsync (MeshQuery.cs:554/612), which is the shape a leaf that cannot observe cancellation would have.
Next reader: the search is now bounded to the Query pool's leaves that MeshNodeLanguageServiceTest's completion path opens (Admin/Partition/script and script/Prefix node-type sources are created in the test output right before the completion) and that do not join on the mesh's teardown token within 30 s.
Activity
rbuergi commentedon Aug 29, 2026
Fresh measurement — only one occurrence so far; too soon to call it either way
Swept every
MeshWeaver Build and Testrun onmain, 2026-08-26T07:38Z → 2026-08-28T20:46Z (61 h,121 non-cancelled attempts, 19 failures, each cross-checked via the shard job's own
Test failed/crash annotations).MeshNodeLanguageServiceTest.ScriptCompletions_FilterByTypedPrefix_NotJustTheAlphabetappearsexactly once: run 33171489378,
2026-08-28T12:32:28Z, shard 2 — the same event this issue was filed from. Since then: 18 valid
mainattempts, 0 recurrences (through 2026-08-28T20:46Z; no furthermainpush has run as ofthis comment, 2026-08-29T06:14Z). Since the issue was filed (14:18Z): 11 valid attempts, 0
recurrences.
That is well short of a confident window — the rule of thumb here is ~30 clean runs before calling
something not-reproducing, and no fix has landed since (
#2598/102275d1eis still the most recentcommit touching
MeshNodeLanguageService.cs/KernelScriptReferences.cs). One occurrence is notenough to confirm the race is real and recurring either, versus a one-off under whatever load shard
2 was carrying at that moment.
Leaving open — could not establish a rate in either direction with this little data. Worth
re-checking once more
mainruns accumulate.rbuergi commentedon Aug 29, 2026
The premise is refuted: #2598 did not start this, and the test did not fail its assertion
MEASURED, from the still-live
testResults-shard2artifact of the exact run this issue was filed on (33171489378).1. The test PASSED. There was no completion assertion failure.
collected-logs/_meshweaver-test-trace.log, lines 5921–5936:All four assertions (
NotBeEmpty,Count ≤ 15,Contain "Mesh",OnlyContain "Mes") were green in 1.04 s. The reported exception is anInvalidOperationExceptionfromMonolithMeshTestBase.DisposeAsync()(MonolithMeshTestBase.cs:1574) — a teardown failure wearing the test's name. Nothing about the reference set was ever observed to be wrong. The "truncated symbol list" hypothesis is refuted at the observation level.2. It predates #2598 by ~4 hours — it is a recurrence of #2578
cc3c18c3)?842cd523merge-base --is-ancestor)4ad8fb85#2598 merged at 11:34Z. #2578 is the observation that motivated #2598 — same test, same exception text, same 30 s, same anonymous
1 pooled I/O leaf(s). So "first seen right after #2598" is inverted: it predates that PR and recurred unchanged after it. This should be treated as a recurrence of #2578.3. The
Lazy<Task<T>>cancellation hypothesis is refuted at the code level tooBoth memos are exactly the shape worth suspecting —
KernelScriptReferences.cs:64andMeshNodeLanguageService.cs:68— but the "cancelled task cached forever" classic does not apply:Task.WaitAsync(ct)returns a new task that faults with OCE and never touches the awaited task, and neither memo is started with a caller token (Task.Run(CreateSharedSnapshot)takes none;BuildScriptWorkspaceAsyncawaits with explicitCancellationToken.None). A cancelled caller cannot poison either memo.🚨 4. Why this has now cost two investigations: #2598's own diagnostic is inert
#2480 asked for the offending pool to be named. #2598 added it —
IoPoolRegistry.cs:149, "IoPoolDrain: pool '{PoolName}' did not finish {Residual} leaf(es)…". On the first recurrence it produced nothing: zero hits in the job log, the trx, andcollected-logs/.Cause: it goes to
ILogger, andDrainAll()runs afterMesh.Dispose(). MEASURED: of 294Mesh.Dispose() invokingwindows in this shard's trx, ZERO contain an ILogger line at any level — while the same trx carries 84[Warning] [SynchronizationStream]and 76[Warning] [RoutingServiceBase]records from before dispose. The mesh's log sink stops capturing at dispose, so the one diagnostic written to name the pool is structurally invisible in exactly the window it exists for.PR #2665 fixes that:
DrainAllreturns the per-pool residuals, and both callers route them somewhere that survives dispose (TestPhaseTrace→ the file CI keeps, which also survives anexit=124host kill;TeardownReport.ResidualByPoolin production). The trace becomesleakedIoLeaves=1 pools=[Query=1]. That matters becauseQuery=1andCompile=1are different bugs, andIoPool.Drainyields a residual from three distinct causes that all currently reduce to the same integer.5. What the evidence says the leaf probably is (NOT proven — needs the name)
DrainTimeout(IoPool.cs:80).MeshNodeLanguageService.cs:153callsusageIndex.EnsureFresh(), andCompletionUsageIndex.EnsureFresh()(:79) fires a fire-and-forget, never-disposed, unownedmesh.Query<MeshNode>(...)subscription with.Timeout(QueryTimeout)whereQueryTimeout = 30 s(:43), subscribed through the Query pool. A second independent 30 s budget coinciding with the drain's is the expected shape.CompletionMemoryStorehas the same unowned.Subscribe(...)at:102,:182,:265.If #2665 lands and the next occurrence says
pools=[Query=1], that candidate is confirmed and the fix is to giveEnsureFresh()an owner (hold theIDisposable, dispose the prior one, dispose on teardown) rather than leaving a 30 s subscription running past the request that started it.6. One thing #2598 made worse, worth a look regardless
Task.Run(CreateSharedSnapshot)and theCancellationToken.Nonecontinuation now run on bare ThreadPool items that noIIoPooltracks andDrainAll()cannot see or join, while capturinghub.ServiceProvider. The PR moved the largest Roslyn workload in the process out of the drain's sight rather than making it drainable. Evidence it runs pastTEST_END:DISPOSE_MEM rss Δ+161 MiBon this 1-second test, versus Δ+9 / Δ+27 MiB on the same class's other instances.Recommendation: retitle/close this as a duplicate of #2578, land #2665 so the third occurrence names its pool, and do not re-run, widen
DrainTimeout(the guard's own message forbids it), or skip-list the test — the test is passing; teardown is not.Merge pull request #2665 from Systemorph/fix/2616-drain-residual-name…
[-]Flake: MeshNodeLanguageServiceTest.ScriptCompletions_FilterByTypedPrefix_NotJustTheAlphabet on shard 2 — first seen right after #2598 touched MeshNodeLanguageService[/-][+]MeshNodeLanguageServiceTest teardown DIRTY — one pooled I/O leaf still ignores cancellation after #2598 (#2665 names the pool on the next occurrence)[/+]rbuergi commentedon Aug 30, 2026
Second face, pool NAMED (#2665 delivered):
DocumentBlocksAreaRenderTestleaks one Layout-pool leafObserved 2026-08-30 on #2733 (run 33300432494, shard 4, head
4d7812de6). The PR moves themodules/generations GC off the readiness path (MeshWeaver.PluginCatalog+Memex.Portal.Shared) —MeshWeaver.ContentCollections.Indexing.Graph.Testhas no project-reference path to either assembly (checked every csproj in its closure;MeshWeaver.Graph.csprojmentionsMemex.Portal.Sharedonly in a comment), so the diff is not loaded in the failing host. Main was green atd3272b5f8(07:28) anda0d404b89(07:55).The test PASSED; teardown found the leak — the #2616 shape exactly, on a different test, and this time the pool is named (
_meshweaver-test-trace.log,testResults-shard4):So the leaf that will not cancel is a
IoPoolNames.LayoutSubscribeThroughPoolleaf — the layout-area render subscribe (RenderBlocksUntil→LayoutAreaReferenceover the Document blocks area), not a script-completion /MeshNodeLanguageServiceleaf. That refutes the remaining "#2598 started this" thread here: the common factor is the render-subscribe leaf ignoring the pool's cancellation at drain, andMeshNodeLanguageServiceTestis just one of the tests that renders.Also in the stdout for this test (before the DIRTY teardown): three
ROUTER_TRAFFICerrors —RawJsonwith the mesh hub as sender andDataChangedEvent/SubscribeAckwith the mesh hub as target, forTestData/content/_Documents/vertrag_2025.txt— i.e. the render's data-change subscription is wired through the mesh (router) hub rather than a hub of its own, which is the likeliest place a subscribe leaf sits outside the drain's cancellation.Not re-run; the PR stays red until this is fixed or a fresh run happens to miss the race.
Filed by Claude Code from #2733.
rbuergi commentedon Aug 30, 2026
New occurrence: 2026-08-30, on #2760 (a permission-fold security fix), shard 2
Same test, same signature, same 30 s drain:
Run: https://github.com/Systemorph/MeshWeaver/actions/runs/33308671083/job/99250133646 · commit
e0cd6e9f5· 11:28:39Z.Attribution evidence, so the next reader does not have to redo it
eace590ecarries every src change in that PR andRun tests (shard 2)was success; the red commite0cd6e9f5differs from it by three files intest/MeshWeaver.Security.Test/only — a different assembly fromMeshWeaver.Hosting.Monolith.Test.=== TEST END ===is logged with no assertion failure; only the classDisposeAsyncfails, 30 s later. Whatever the leaf is, it is not the completion assertion.main(be39fc607) — single test ×5 each, and the whole class underDOTNET_PROCESSOR_COUNT=4×5 each. It does not reproduce on this Mac even under the repo's usual CI-race lever, which reinforces that the trigger is shard-level load rather than anything in the class.Still unnamed
Consistent with the note in the title, this occurrence still does not name the pool. The only
IoPoolstack in the shard log belongs toMeshWeaver.Hosting.Test'sFileSystemStorageAdapter.WriteAtomicdiagnostic — a different, passing project — so it is not evidence about this leaf. Whatever #2665 was expected to surface did not appear here either.Cost recorded: this is now a second PR blocked by this flake — a security fix (a live authorization bypass) held out of
mainbehind it, in a window where merge slots are being rationed because CD is starving.🤖 Generated with Claude Code
rbuergi commentedon Aug 30, 2026
Third occurrence, 2026-08-30 12:48Z on #2766 (shard 1) — and this time the pool IS named
Run https://github.com/Systemorph/MeshWeaver/actions/runs/33312105107/job/99259302099 · commit
664494d6b(a PR whose diff isdeploy/homebrew/**,.github/workflows/homebrew.yml, docs and two doc-test guards — nothing that can reach a script-completion test; main's last three Build-and-Test runs0c584a2d,d9ef9503,d1a82597are green).Same signature: the test passes (
=== TEST END … ScriptCompletions_FilterByTypedPrefix_NotJustTheAlphabet ===at 12:48:27.592, no assertion),Mesh.Dispose()runs the full 30 s drain,DisposeAsyncthrowsteardown DIRTY — 1 pooled I/O leaf(s) still running.#2665's naming did fire — in the trace artifact, not the job's stdout, which is why the previous occurrence read it as absent.
testResults-shard1/collected-logs/_meshweaver-test-trace.log:So the leaf that outlives the drain is on the Query pool — a
MeshQuery/IMeshService.Queryleaf, not the script-completion/reference build #2598 touched (that runs on the compile lane).teardown-stragglers.logcaptured nine first-chance exceptions in this shard, at 12:46:46 and 12:50:44–50 — none inside the 12:48:27–12:48:57 window, so the Query leaf never threw: it is parked, not faulting. TheMeshQuery.MergeProviderObservablesframes that do appear in the other captures rideObservable.FromAsync/StartAsync(MeshQuery.cs:554/612), which is the shape a leaf that cannot observe cancellation would have.Next reader: the search is now bounded to the Query pool's leaves that
MeshNodeLanguageServiceTest's completion path opens (Admin/Partition/scriptandscript/Prefixnode-type sources are created in the test output right before the completion) and that do not join on the mesh's teardown token within 30 s.fix(#2616): the script reference set must not freeze the assembly list (