Repository navigation
A ClickedEvent whose circuit is gone is DROPPED silently — while the action it triggers is fire-and-forget and outlives the circuit #3566
Description
What happened
A ClickedEvent whose originating circuit has been disposed is dropped with a warning and nothing
else — while the action it would have triggered is deliberately fire-and-forget and outlives the
circuit. So a user who clicks a button and immediately navigates away gets a silent nothing, and
there is no way for them or for anyone reading the UI to tell the difference between "it ran" and "it
was discarded".
Measured on MeshWeaver.Education run
34042620439, job
101512796719 — the Store Install button.
Playwright trace, attempt 1:
15:44:38.03 Is visible … getByRole('button', {name: /^(Install$|Update to)/}) → true
15:44:38.03 Click … locator resolved to <button …>, performing click action, click action done (no error)
15:44:38.06 Navigate to "/Store" ← 20 ms later
Portal log, the next five seconds:
15:44:38.205 Circuit connection DOWN: id=_XUK2rPty6NdzA3VHePOWQcrxate5THzbdSGQ2rZKeo
15:44:38.205 Circuit closed … / Disposing per-circuit portal hub portal/_XUK2… on circuit close
15:44:38.362 Orleans: delivering RawJson to e2e-admin/Packages, sender=portal/_XUK2…
15:44:38.580 [ROUTE] Directed delivery to pod hub 'portal/_XUK2…' was refused: its OWNER RELEASED the address
15:44:43.410 warn: Dropping ClickedEvent for stream JRGdthy_Aka7g8BnYLXctQ on hub e2e-admin/Packages:
no synchronization hub found on this hub or any parent — the target stream is gone
(disposed circuit, released read stream, or never-created sync hub).
Drop site: src/MeshWeaver.Data/DataExtensions.cs:488-494 (LogStreamMessageDrop), reached from
:440. WithClickAction's body — here CatalogLayoutAreas.InstallPackage — is never invoked.
Why this is not simply "the circuit was gone, so of course"
🚨 The action does not need the circuit, and the framework already knows that. InstallPackage
ends in RunAsSystem(...).Subscribe(...) — fire-and-forget. In the very same run, the retry's install
ran to completion 850 ms AFTER its own circuit closed. So the handler is perfectly capable of
outliving the circuit; the only thing the disposal removes is the path the event takes to reach it.
🚨 And it is not decided by how fast you navigate. The retry had a tighter window — 4 ms
click→navigate, circuit closed 90 ms after the click — and it won. What differed was load: the
failing attempt's card locator took 2 939 ms to settle versus 905 ms on the retry, i.e. the
page was still streaming. So the outcome turns on server load at the moment of the click, which is
precisely the shape that makes it rare in testing and reachable in production.
The denominator
Dropping ClickedEvent appears exactly once in run 34042620439 — that click — and zero times
in run
34080328179, where the
same install succeeded. One occurrence, one failure; zero occurrences, zero failures.
Why it belongs in the "half-lands and nothing notices" family
The 5-second sync-hub grace means the drop is logged after the event arrived, at Warning, on the
server. The clicking user sees an unchanged page. Nothing retries, nothing surfaces, and the next
thing that reads the expected outcome — a probe, another view, a person — sees an absence that looks
exactly like "you never clicked". In the run above that cost a 300-second probe of a package that was
never going to exist.
What is NOT being proposed
- Not a retry timer or a resubscribe. If the event is genuinely undeliverable, the fix is not to
poll for a stream that is gone. - Not widening the sync-hub grace. That is a bound, and moving it moves the cliff.
The scope call, which is the maintainer's
Two coherent answers, and this issue does not pick one:
- Deliver it anyway. An action bound with
WithClickActionthat does not read back through the
originating stream has no dependency on that stream; routing the event to the target hub without
requiring a live sync hub would make the click land. The risk is actions that DO write back to the
caller's stream and would then complete into nothing. - Refuse it visibly. Keep the drop, but stop it being silent — surface it to whatever is still
attached, and/or raise it above Warning so an operator sees a lost user action rather than a
routine line.
Whichever is taken, the current state — the action is capable of running, the event is thrown away,
and only a Warning five seconds later records it — is the one that should not stand.
Provenance
Measured 2026-09-07 from the full job log, the portal log and both retained Playwright traces of run
34042620439, plus run 34080328179 and today's green run 34088440149 as controls. Filed out of
MeshWeaver.Education#275, whose own text says bundling would give one issue two root causes — the
harness half (a Store install click with no outcome check and no wait, unlike its sibling install()
helper which retries in rounds) stays there.
Activity
rbuergi commentedon Sep 7, 2026
Fixed and merged — closing by hand (the merge queue does not fire
Closes)PR #3601 merged. A
ClickedEventwhose Blazor circuit has gone is now REFUSED to its sender — aDeliveryFailurethe portal surfaces as the standard error modal, plus oneErrorline naming theaction and the area — instead of being dropped while the fire-and-forget action it triggers outlives
the circuit.
The scope call, recorded because it was a judgement and not a measurement. The issue named two
options: deliver the action anyway, or refuse visibly. This took refuse visibly, on the grounds
that a dropped action the user believes succeeded is the worse failure. The maintainer confirmed that
direction before it landed. If the intent was ever that the action should still run, this is the
wrong half and should be re-cut rather than extended.
Data-sync traffic deliberately keeps the historical silent drop — a data frame for a subscriber
that has gone away wants no audience, and NACKing every one would be pure teardown noise. That
control is asserted as its own test, and it is what makes this a measurement of the user-action
class rather than of "everything now NACKs".
Verified before merge:
DroppedUserActionIsRefusedTest3/3 (including the control and the markertest that a fourth user-action event added later cannot silently rejoin the churn class);
MeshWeaver.Documentation.Test305/305;-warnaserrorclean on all three touched projects.Closed 2026-09-07 during a fleet-wide sweep.