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
Flake: PodHubTransportTest.CrossSiloNack_ReachesASenderWhoseStreamSubscriptionIsGone — red on shard 0 with a control arm that isolates it #3298
MeshWeaver.Hosting.Orleans.Test.PodHubTransportTest.CrossSiloNack_ReachesASenderWhoseStreamSubscriptionIsGone
failed Run tests (shard 0) on #3293 (job 101030012474, started 2026-09-04T12:53:28Z). It is not
in .github/known-flakes.json.
Why this is a race and not the PR's doing — the control arm
Two heads of the SAME PR, minutes apart:
head
Run tests (shard 0)
shards 1–4
616e39613
success
all success
4054f1ec9
failure
1, 3 success (2, 4, 5 still running)
The entire diff between those two heads is test/MeshWeaver.Documentation.Test/ModuleIdentityPublishGuard.cs — assertions over a workflow
file's TEXT, in a different assembly, adding no new test methods (so no resharding). There is no path
from that edit to Orleans pod-hub transport. The tree that passed and the tree that failed are the
same tree as far as this test can observe.
Per AGENTS.md I have not re-run it: "Re-running to 'see if it was a flake' hides the bug — flakes are
real races." Filing the race instead.
Where to start
The name says the subject: a cross-silo NACK must still reach a sender whose stream subscription is
already gone. That is a teardown-ordering window, and it neighbours two known live problems:
podhub-shuttingdown-verdict-disables-dead-circuit-eviction — the pod-hub answering ShuttingDown
suppresses the eviction that would clear the circuit.
the shard-0 Orleans flake cluster (2026-08-09), recorded as only PARTLY resolved.
Worth checking whether the NACK path observes the same ShuttingDown verdict, in which case the
sender is unreachable by construction for a window rather than intermittently.
Note on the catalogue
I have deliberately NOT added it to .github/known-flakes.json. Cataloguing buys a steward re-queue,
and doing that to get my own PR through would be exactly the band-aid the house rules forbid — the
entry should follow a root cause, not replace one. If someone with context decides the re-queue is
worth it before the fix, that is a call to make explicitly.
Found while landing #3293 (the module-pack identity anchor), which is currently the thing holding
core CD's delivery.
System.TimeoutException : Timed out after 00:00:30: a request the router gave up on must come back
to its cross-silo sender as a DeliveryFailure. Silence here is issue #1742 exactly: the NACK was
published onto the sender's own subscriber-less stream, succeeded, and was discarded — so the caller
waits out its whole budget for an answer the router believes it sent
at PodHubTransportTest.WaitUntil(...) PodHubTransportTest.cs:372
at PodHubTransportTest.CrossSiloNack_ReachesASenderWhoseStreamSubscriptionIsGone() :311
Assembly summary: Total: 251, Errors: 0, Failed: 1, Skipped: 0, Time: 62.589s — so the host ran
normally and exactly this one test timed out. The test's own two controls (a cross-silo PingRequest
proving the pod-hub claim is live, and a unique stray- token proving the subscriber-less stream
really cannot deliver) both PASSED before the failing wait, which is what makes this a clean signal:
the setup was in the intended state and the directed NACK still did not arrive within 30 s.
A lead worth checking first
main landed teardown/quiesce work in the same window this failed
(Doc/Architecture/TeardownLayers.md, ActivityTrackerQuiesceTest, HostedHubDisposalJoinTest, MessageHubTest). This test's whole subject is a NACK racing a sender whose subscription is already
gone — teardown ordering is the same neighbourhood. Two possibilities worth separating before
assuming an old race:
the flake predates that work and is the shard-0 Orleans cluster resurfacing, or
the teardown change altered when the sender's route is torn down relative to PostFailure.
main's own Build and Test was green at b0d4a826f (12:46Z), so whatever this is, it is
intermittent rather than a standing regression — which does not make it less real.
Status of the PR that surfaced it
#3293 was behind main by 8 commits. I merged main in rather than re-running the failed job, per
AGENTS.md's "merge main only when the PR is DIRTY or CI fails on something your diff cannot reach" —
that re-runs CI against a tree main has proven green instead of rolling the dice on the same tree.
If it comes back red on this test again, that is a much stronger signal than the first occurrence and
should be treated as reproducible.
Correction: my own "check the teardown work first" lead is falsified
Earlier I suggested #3291 (fix/robust-disposal, merged to main 12:26:03Z) as the first thing to
check, on the grounds that it landed in the same window and this test's subject — a NACK racing a
sender whose subscription is gone — is teardown-shaped. That correlation does not survive the
ancestry check:
The tree that failed did not contain the disposal work, and the tree that did contain it passed.
So #3291 is not implicated, and the run carrying it is evidence against the story rather than for
it. I was pattern-matching on "teardown-shaped test + teardown-shaped change in the same hour", which
is [[frequency is not causation]] with better vocabulary.
What stands unchanged: the failure is intermittent and reproducible-by-luck rather than by tree —
two heads whose only difference is a string-assertion test in a different assembly disagree on it.
Both of the test's own controls passed before the failing wait, so the setup reached the intended
state and the directed NACK still did not arrive within 30 s.
What to start from now, absent the false lead: the pod-hub ShuttingDown verdict path and the
shard-0 Orleans flake cluster (recorded as only partly resolved) remain the standing candidates, but
neither is evidenced yet — treat them as untested guesses, not as leads.
Related but almost certainly separate, filed here only so the next reader can rule them in or out
rather than rediscover them: NackReachesTheWaiterDuringTeardownTest.OwnerDisposingUnderMeshTeardown_StillAnswersTheWaitingCaller
(a TimeoutException, currently blocking core #3297) and MeshWeaver.PluginTester.Test.CascadeTest.IndependentNodesRunInParallel_BoundedByTheSlotCount
(which failed a merge-queue group build and got #3296queue-rejected). Three intermittent failures
in one afternoon is worth someone asking whether they share a cause — but I have no evidence that
they do, and saying otherwise would repeat the mistake above.
Checked against #3303, and they are NOT the same defect
#3303 asked whether this shares a root cause with NackReachesTheWaiterDuringTeardownTest — "an answer owed to a waiting caller is dropped once the
owner is shutting down". That sentence fits both. The mechanism does not, on three independent
counts, each read off the code rather than inferred:
HierarchicalRouting.RouteAlongHostingHierarchy — the arm that refuses to hand a delivery to a parent past DisposeHostedHubs, reported by MessageService.ReportFailure
RoutingGrain.PostFailure — local route → directed pod-hub call → grain transport → stream fallback. It never touches HierarchicalRouting.
precondition
a hub in the chain is past DisposeHostedHubs
nothing is shutting down. This test erases a stream subscription on purpose, on a healthy cluster, and both of its own controls (the cross-silo PingRequest arriving, the stray- token NOT arriving) pass first.
the waiter
an armed LatePatchResponseRegistry entry in the same process
a RegisterStream callback in the routing service's local route table on another silo
The third row is the decisive one. #3303's cure is an in-process hand-over to a registry the waiter
armed before the request was posted. Here nothing arms that registry — the waiter is a stream
registration for a PingRequest, and the message is a router-minted DeliveryFailure. So even if
the hand-over ran on this path it would find nothing to hand the NACK to, and it does not run on
this path at all.
#3302 / 684cc6ac1 therefore does not fix this, and this issue stays open. I have not re-run it
(AGENTS.md), and I have not catalogued it in .github/known-flakes.json.
Where I would start, given the above
Not with the teardown work — the same ancestry check that falsified the #3291 lead applies here, and
this test's premise is a HEALTHY cluster. The subject is a directed pod-hub call that either lands
or answers, so the question is which of PostFailure's four transports the NACK took and what that
one returned. RoutingGrain.PostFailure writes a trace tag per branch
(FAILURE_LOCAL_ROUTE, and the pod-hub / grain / stream arms below it) precisely so this is
readable rather than inferable — a run that captures those tags for the failing delivery id would
discriminate "the pod-hub activation answered PodHubNotHere" from "the grain resolve gate declined
and it fell back to the subscriber-less stream" (which is #1742's residual, wearing its cure's
clothes) without another guess.
The pod-hub ShuttingDown verdict lead recorded above is still untested, and worth noting it
requires a shutting-down pod hub that this test does not create — so it needs its own evidence
before it is a lead rather than a neighbour.
Fixed — #3313, merged as 42f0cbc4edd11492a0b285ce3af2fbe4f72fe976
(Closing by hand: the merge queue does not fire Closes links, so the keyword in the PR body did
not act.)
Root cause: a missing precondition in the test, not a transport defect.
RegisterStream establishes two things on different clocks — the local route synchronously, and the
cluster-wide pod-hub claim asynchronously, on a capped backoff (100 ms → 2 s) with no give-up on a
silo. Until the claim lands, [PreferLocalPlacement] puts a directed IPodHubGrain call on the caller's silo, which has no local route for the address, and it answers PodHubNotHere. PostFailure then falls back to the stream — which this test has deliberately emptied — and the NACK
is discarded in silence. That is the reported failure exactly: a 30 s timeout with both controls green.
Why the probe could not catch it, which is the part worth keeping. It proved reachability, and
during that window reachability is satisfied by the stream — the transport the test erases three
lines later. So it was green whether or not the claim had landed. It is also adversarial to the
claim it appeared to prove: it posts from the other silo in a loop, and each failed directed call
mints a throw-away activation there that the owner's next Attach must bounce off, restarting the
backoff. Hence "reproducible by luck rather than by tree" — the control arm in this issue was reading
a scheduling race, correctly.
Measured rather than argued. With one bounce forced and the claim backoff pinned long, the old
probe goes green in under a second while PodHubClaimSettled has demonstrably not completed — 6 runs, 6 times. I did not commit that experiment: its setup depends on an activation-lifetime
ordering I cannot enforce, so as a permanent test it would risk becoming the next flake.
The fix waits on PodHubClaimSettled, the positive signal for "the claim stopped attempting" —
which OrleansCrossSiloReplyTest already waits on before its own directed cross-silo delivery. SiloHostedHub_ReceivesDelivery_EvenWithItsStreamSubscriptionGone had the same latent gap (it waits
for the stream subscription, a different condition that merely takes similar time) and got the same
wait. No production changes.
Your three standing candidates, all ruled out on evidence rather than left hanging:
the pod-hub ShuttingDown verdict — not implicated. It needs a shutting-down pod hub, and this
test's premise is a healthy cluster. It stays a neighbour, not a lead.
The rule is written up in Doc/Architecture/OrleansTestRoutingPattern → "Reachability is not a
claim — wait for the transport you are about to depend on", so the next test that isolates one
transport to assert another finds it before filing a flake.
It was not catalogued in .github/known-flakes.json, and should not be — it was a real defect
with a deterministic cause.
Activity
rbuergi commentedon Sep 4, 2026
The assertion, verbatim
Assembly summary:
Total: 251, Errors: 0, Failed: 1, Skipped: 0, Time: 62.589s— so the host rannormally and exactly this one test timed out. The test's own two controls (a cross-silo
PingRequestproving the pod-hub claim is live, and a unique
stray-token proving the subscriber-less streamreally cannot deliver) both PASSED before the failing wait, which is what makes this a clean signal:
the setup was in the intended state and the directed NACK still did not arrive within 30 s.
A lead worth checking first
mainlanded teardown/quiesce work in the same window this failed(
Doc/Architecture/TeardownLayers.md,ActivityTrackerQuiesceTest,HostedHubDisposalJoinTest,MessageHubTest). This test's whole subject is a NACK racing a sender whose subscription is alreadygone — teardown ordering is the same neighbourhood. Two possibilities worth separating before
assuming an old race:
PostFailure.main's own Build and Test was green atb0d4a826f(12:46Z), so whatever this is, it isintermittent rather than a standing regression — which does not make it less real.
Status of the PR that surfaced it
#3293 was behind
mainby 8 commits. I mergedmainin rather than re-running the failed job, perAGENTS.md's "merge main only when the PR is DIRTY or CI fails on something your diff cannot reach" —
that re-runs CI against a tree
mainhas proven green instead of rolling the dice on the same tree.If it comes back red on this test again, that is a much stronger signal than the first occurrence and
should be treated as reproducible.
rbuergi commentedon Sep 4, 2026
Correction: my own "check the teardown work first" lead is falsified
Earlier I suggested #3291 (
fix/robust-disposal, merged tomain12:26:03Z) as the first thing tocheck, on the grounds that it landed in the same window and this test's subject — a NACK racing a
sender whose subscription is gone — is teardown-shaped. That correlation does not survive the
ancestry check:
f9d2a335a(#3291)?Run tests (shard 0)616e396134054f1ec9061b020c1The tree that failed did not contain the disposal work, and the tree that did contain it passed.
So #3291 is not implicated, and the run carrying it is evidence against the story rather than for
it. I was pattern-matching on "teardown-shaped test + teardown-shaped change in the same hour", which
is [[frequency is not causation]] with better vocabulary.
What stands unchanged: the failure is intermittent and reproducible-by-luck rather than by tree —
two heads whose only difference is a string-assertion test in a different assembly disagree on it.
Both of the test's own controls passed before the failing wait, so the setup reached the intended
state and the directed NACK still did not arrive within 30 s.
What to start from now, absent the false lead: the pod-hub
ShuttingDownverdict path and theshard-0 Orleans flake cluster (recorded as only partly resolved) remain the standing candidates, but
neither is evidenced yet — treat them as untested guesses, not as leads.
Related but almost certainly separate, filed here only so the next reader can rule them in or out
rather than rediscover them:
NackReachesTheWaiterDuringTeardownTest.OwnerDisposingUnderMeshTeardown_StillAnswersTheWaitingCaller(a
TimeoutException, currently blocking core #3297) andMeshWeaver.PluginTester.Test.CascadeTest.IndependentNodesRunInParallel_BoundedByTheSlotCount(which failed a merge-queue group build and got #3296
queue-rejected). Three intermittent failuresin one afternoon is worth someone asking whether they share a cause — but I have no evidence that
they do, and saying otherwise would repeat the mistake above.
rbuergi commentedon Sep 4, 2026
Checked against #3303, and they are NOT the same defect
#3303 asked whether this shares a root cause with
NackReachesTheWaiterDuringTeardownTest— "an answer owed to a waiting caller is dropped once theowner is shutting down". That sentence fits both. The mechanism does not, on three independent
counts, each read off the code rather than inferred:
HierarchicalRouting.RouteAlongHostingHierarchy— the arm that refuses to hand a delivery to a parent pastDisposeHostedHubs, reported byMessageService.ReportFailureRoutingGrain.PostFailure— local route → directed pod-hub call → grain transport → stream fallback. It never touchesHierarchicalRouting.DisposeHostedHubsPingRequestarriving, thestray-token NOT arriving) pass first.LatePatchResponseRegistryentry in the same processRegisterStreamcallback in the routing service's local route table on another siloThe third row is the decisive one. #3303's cure is an in-process hand-over to a registry the waiter
armed before the request was posted. Here nothing arms that registry — the waiter is a stream
registration for a
PingRequest, and the message is a router-mintedDeliveryFailure. So even ifthe hand-over ran on this path it would find nothing to hand the NACK to, and it does not run on
this path at all.
#3302 /
684cc6ac1therefore does not fix this, and this issue stays open. I have not re-run it(AGENTS.md), and I have not catalogued it in
.github/known-flakes.json.Where I would start, given the above
Not with the teardown work — the same ancestry check that falsified the #3291 lead applies here, and
this test's premise is a HEALTHY cluster. The subject is a directed pod-hub call that either lands
or answers, so the question is which of
PostFailure's four transports the NACK took and what thatone returned.
RoutingGrain.PostFailurewrites a trace tag per branch(
FAILURE_LOCAL_ROUTE, and the pod-hub / grain / stream arms below it) precisely so this isreadable rather than inferable — a run that captures those tags for the failing delivery id would
discriminate "the pod-hub activation answered
PodHubNotHere" from "the grain resolve gate declinedand it fell back to the subscriber-less stream" (which is #1742's residual, wearing its cure's
clothes) without another guess.
The pod-hub
ShuttingDownverdict lead recorded above is still untested, and worth noting itrequires a shutting-down pod hub that this test does not create — so it needs its own evidence
before it is a lead rather than a neighbour.
rbuergi commentedon Sep 4, 2026
Fixed — #3313, merged as
42f0cbc4edd11492a0b285ce3af2fbe4f72fe976(Closing by hand: the merge queue does not fire
Closeslinks, so the keyword in the PR body didnot act.)
Root cause: a missing precondition in the test, not a transport defect.
RegisterStreamestablishes two things on different clocks — the local route synchronously, and thecluster-wide pod-hub claim asynchronously, on a capped backoff (100 ms → 2 s) with no give-up on a
silo. Until the claim lands,
[PreferLocalPlacement]puts a directedIPodHubGraincall on thecaller's silo, which has no local route for the address, and it answers
PodHubNotHere.PostFailurethen falls back to the stream — which this test has deliberately emptied — and the NACKis discarded in silence. That is the reported failure exactly: a 30 s timeout with both controls green.
Why the probe could not catch it, which is the part worth keeping. It proved reachability, and
during that window reachability is satisfied by the stream — the transport the test erases three
lines later. So it was green whether or not the claim had landed. It is also adversarial to the
claim it appeared to prove: it posts from the other silo in a loop, and each failed directed call
mints a throw-away activation there that the owner's next
Attachmust bounce off, restarting thebackoff. Hence "reproducible by luck rather than by tree" — the control arm in this issue was reading
a scheduling race, correctly.
Measured rather than argued. With one bounce forced and the claim backoff pinned long, the old
probe goes green in under a second while
PodHubClaimSettledhas demonstrably not completed —6 runs, 6 times. I did not commit that experiment: its setup depends on an activation-lifetime
ordering I cannot enforce, so as a permanent test it would risk becoming the next flake.
The fix waits on
PodHubClaimSettled, the positive signal for "the claim stopped attempting" —which
OrleansCrossSiloReplyTestalready waits on before its own directed cross-silo delivery.SiloHostedHub_ReceivesDelivery_EvenWithItsStreamSubscriptionGonehad the same latent gap (it waitsfor the stream subscription, a different condition that merely takes similar time) and got the same
wait. No production changes.
Your three standing candidates, all ruled out on evidence rather than left hanging:
684cc6ac1does not fix this, and the cause waselsewhere entirely.
ShuttingDownverdict — not implicated. It needs a shutting-down pod hub, and thistest's premise is a healthy cluster. It stays a neighbour, not a lead.
The rule is written up in
Doc/Architecture/OrleansTestRoutingPattern→ "Reachability is not aclaim — wait for the transport you are about to depend on", so the next test that isolates one
transport to assert another finds it before filing a flake.
It was not catalogued in
.github/known-flakes.json, and should not be — it was a real defectwith a deterministic cause.
🤖 Filed from Claude Code.