Repository navigation
ThreadTokenUsageTest is wall-clock-bounded but missing from ConcurrencyStressCollection — reddens unrelated PRs #2001
Description
ThreadTokenUsageTest judges a cancel on a 10-second wall clock while sharing a 4-vCPU runner with three other AI.Test classes. It is not in ConcurrencyStressCollection, and it should be — or its wait should stop being wall-clocked.
Seen on #1991's CI (run 32490339312, shard 3), where it was the one failure in that PR's shard that the PR could not have caused.
What failed
MeshWeaver.AI.Test.ThreadTokenUsageTest.CancelledRound_RecordsTokensConsumedBeforeCancel_OnCellAndThread [FAIL]
Expected the observable to emit a value matching the predicate within 10s,
but it did not. The observable emitted nothing at all.
at ThreadTokenUsageTest.cs:262
Line 262 is the usage-satellite wait after the cancel:
await WaitForUsage(threadPath, "usage_cancel_model",
u => u.InputTokens == InTokens && u.OutputTokens == OutTokens, 10_000);It ran out of time. It did not read a wrong value — the preceding WaitForThread(... Cancelled, 20_000) had already passed, so the cancel itself landed; only the satellite had not appeared inside its own, tighter 10s budget.
Why it is structural, not luck
8807a6d8c ("Give the five machine-hungry AI.Test classes the box, and parallelise the rest", 2026-08-10) turned parallelism on for the whole assembly:
{ "parallelizeAssembly": false, "parallelizeTestCollections": true,
"maxParallelThreads": 4, "methodTimeout": 30000 }…and serialised exactly five classes into ConcurrencyStressCollection: AgentChatClientDeadlockTest, CrossHubPatchAtomicityTest, DelegationDeadlockTest, InboxToolIntegrationTest, PatchWorkspaceAckTest.
ThreadTokenUsageTest carries no [Collection] attribute, so since that commit it runs up to 4-wide on a 4-vCPU CI runner — while still judging a cancel-then-satellite round trip on a fixed 10 000 ms.
That commit's own message names this failure mode precisely:
a handful of classes deliberately saturate the machine and then judge the result on a wall clock, and four of those timesharing one 4-vCPU runner blow bounds that hold with room to spare when each has the box
The membership rule it states is structural, not "whatever failed last time" — which is exactly why this belongs in the set rather than being re-attributed once per PR. It also has form: #1812 was a different defect in this same test's satellite read.
Locally, uncontended, the class is 10/10 in 9 s.
Two ways to fix it — either, not both
[Collection(ConcurrencyStressCollection.Name)]on the class. One line, consistent with the five already there, and honest about what the test is: a timing assertion that needs the box. Costs some wall clock on the shard.- Make the wait event-driven instead of wall-clocked. The satellite write is an observable event; a bound that exists only to stop a hang can then be generous without weakening anything, because the assertion no longer is the bound.
Not raising 10 000 to 20 000. That is the band-aid AGENTS.md forbids: it does not make the test judge the right thing, it just moves the threshold until the next busy runner finds it.
Why this is worth its own issue
Relates to #1384, which is the right complaint at population level — six unrelated single-test failures in ten runs — and which notes that the recurring cost is attribution: each one buys a fresh investigation that ends in the same answer. This one is already attributed, with the commit that created the gap and a one-line fix, so it can be closed rather than re-diagnosed the next time it reddens somebody's PR.
🤖 Generated with Claude Code
Activity
Merge pull request #2006 from Systemorph/fix/2001-usage-wait-bound