Desktop 0.4.26: after a relay reconnect the channel view can go permanently blank — resubscribe reuses the subscription ID, relay silently drops it, UI shows an empty channel with no error
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
On Buzz Desktop 0.4.26 (macOS 15.5, hosted relay `wss://*.communities.buzz.xyz`), a
channel intermittently renders **empty or frozen at an old message** while the relay
demonstrably holds newer events. Outgoing messages from the same app still publish
successfully. The connection indicator stays healthy. The only reliable recovery is a
full app quit + relaunch — reopening the channel, switching channels, and waiting do
nothing.
Because Desktop keeps no local message cache, a dead subscription is visually
**indistinguishable from an empty channel**. That is the part that makes this expensive:
there is no error, no banner, no retry affordance. A user reasonably concludes their
counterpart never replied.
## Reproduction
Not deterministic on demand — it follows a network interruption (sleep/wake, Wi-Fi
change, relay blip). Observed several times over 2026-07-27/28. Sequence:
1. Have a channel open with live traffic.
2. Interrupt connectivity long enough for the relay socket to drop.
3. Let Desktop reconnect on its own.
4. Have another client publish a kind-9 into that channel.
**Expected:** the message appears.
**Actual:** nothing appears, indefinitely. Meanwhile:
```console
$ buzz messages get --channel | tail
# the relay has the message; it was stored and served on REQ
```
Sending from the blind Desktop window still works — the event publishes and other
clients receive it. So it is live fan-out to *this* client that is dead, not the
connection.
## Diagnosis
Desktop links **rust-nostr `nostr-sdk` 0.44.1 / `nostr-relay-pool` 0.44.1** (crate paths
present in the shipped binary). That library's reconnect path re-issues each stored
subscription with **the same `SubscriptionId`** — the strings `Impossible to
resubscribe.`, `Removing subscription.`, `Timeout reached for subscription,
auto-closing.` and `Auto-closing subscription.` are all present in the 0.4.26 binary,
alongside Buzz's own `[relay_reconnect_hook]` and `Heartbeat: reconnected to …`.
That behavior is **correct per NIP-01** — a `REQ` reusing an existing subscription ID is
specified to replace that subscription, and reusing an ID after `CLOSE` is legal.
The problem is that the relay does not honor it. #3019 documents exactly this,
reproduced with minimal WebSocket clients:
> A `REQ` reusing a sub ID that was CLOSEd earlier on the same connection is silently
> ignored. […] Replacing (same-ID re-REQ) or CLOSEing overlapping subs kills delivery
> for survivors.
Chain that together and the symptom follows directly:
```
socket drops
→ nostr-relay-pool auto-closes / re-REQs with the same SubscriptionId
→ relay silently discards the resubscribe (no NOTICE, no CLOSED)
→ transport is healthy, heartbeat logs "reconnected"
→ zero live events forever
→ UI renders an empty channel
```
A fresh process gets fresh subscription IDs, which is precisely why quit + relaunch is
the only thing that clears it.
I have not attached a packet trace to Desktop, so treat the causal chain as a strong
inference from (a) the linked library's documented resubscribe semantics, (b) the
strings in the shipped binary, and (c) the independently reproduced relay behavior in
#3019 — not as instrumented proof of this specific window.
## Asks
The root-cause fix is relay-side and belongs to **#3019** — the relay should honor a
same-ID `REQ` as a replacement per NIP-01. This issue is about the two things Desktop
can do regardless of when that lands:
**1. Never render a silently-dead subscription as an empty channel.** This is the
high-value one. If a subscription has been re-issued and no `EOSE`/events have arrived
within a timeout, surface it — a "reconnecting…" or "connection stale, click to
reload" state. Anything is better than a blank channel that looks like nobody has
spoken. Related but distinct: #3224 notes mobile lacks WebSocket stall detection;
desktop has transport-level detection but nothing at the *subscription* level, which
is where this fails.
**2. Mitigate by generating a fresh subscription ID on reconnect.** #3019's own
write-up names the client pattern that works reliably against the current relay: one
never-reused, never-replaced, never-CLOSEd subscription per channel. This is a
workaround for a relay bug rather than a correctness fix, so it is secondary to (1) —
but it would make the symptom disappear for every user on a hosted relay today,
without waiting on a relay deploy.
A cheap third option, if a cache is on the roadmap anyway: hydrating the channel view
from stored events on open would make a dead live-sub degrade to "stale" instead of
"empty."
## Environment
- Buzz Desktop 0.4.26 (latest release at time of filing), macOS 15.5 (Darwin 25.5.0), Apple silicon
- Hosted relay, `wss://.communities.buzz.xyz`
- `nostr-sdk` 0.44.1, `nostr-relay-pool` 0.44.1, `nostr` 0.44.4 per binary strings
- buzz-acp managed agents on the same machine keep responding normally through the
outage — the agent harness resubscribes successfully where the UI does not, which is
another signal the failure is in the Desktop subscription lifecycle rather than the
network.
## Related
- #3019 — relay live fan-out: closed sub IDs poisoned, replace/CLOSE tears down overlapping delivery (root cause)
- #3206 — multi-channel `#h` filters silently receive zero channel-scoped events
- #3215 — Desktop reconnect replay re-alerts old thread replies (same reconnect path, different symptom)
- #3224 — mobile: no WebSocket stall detection
Contributor guide
Assessment
This issue has not been assessed yet.