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
When an audio room empties, the relay auto-archives the huddle's ephemeral backing channel by writing channels.archived_at directly and emitting only kind 48103. Unlike the two other archive paths, it never calls emit_group_discovery_events, so no kind 39000 carrying ["archived", "true"] is ever published for that channel.
Clients build their channel list from kind 39000, so the database and the event projection diverge permanently:
The relay knows the channel is archived.
Every client — including a fresh install on a new machine — still sees a live channel and keeps <parent> huddle in the sidebar indefinitely.
This does not self-heal. The ephemeral reaper is the one path that would publish the missing discovery update, but its UPDATE filters ch.archived_at IS NULL, so a channel already archived by the audio path is never picked up. The stale 39000 is a replaceable event, so the wrong state is what every future client syncs.
I believe this is the root cause of the "Desktop can keep the channel row visible" behaviour described in #2954. That issue proposes letting kind 9008 through for archived channels; this one is about clients never learning the archive happened in the first place. Fixing this removes the ghost row entirely, and the two fixes are complementary.
Steps to reproduce
Start a huddle in a channel. The relay creates the ephemeral backing channel <parent> huddle (ttl_seconds = 3600).
End the huddle by having every audio peer drop — force-quit the client or drop the socket — rather than the owner ending the huddle. Relay logs audio room empty — auto-ending huddle.
Inspect relay state:
SELECT archived_at FROM channels WHERE id = '<huddle channel>' → set.
The newest kind 39000 for that d tag → no archived tag.
Compare with a huddle the owner ended normally: that one has a kind 9002 and a follow-up 39000 with ["archived", "true"].
Observed on a self-hosted single-relay deployment: of 16 huddle channels, the one that ended via the audio auto-end path was the only ghost; the 15 ended by the owner all converged correctly.
Expected behavior
The audio auto-end path should converge the event projection with the database exactly like the other two archive paths do — i.e. after a successful archive_channel, emit the group discovery events (and, mirroring the reaper, likely also the channel_auto_archived system message and evict_all_channel_subscriptions).
More generally: archive_channel is currently called from three places, and only two of them publish the discovery update. Coupling the republish to the archive itself would make this class of divergence unrepresentable.
Version and platform
Relay: ghcr.io/block/buzz:sha-8342dfc (commit 8342dfcc5890b81a269a8ec3db73a8a56f76ce79), self-hosted, single relay
Buzz Desktop 0.5.5, macOS 26.5.2
Logs / additional context
Code paths at 8342dfcc5890b81a269a8ec3db73a8a56f76ce79:
Audio auto-end archives the channel and emits only kind 48103 — no discovery republish:
Workaround for an already-stuck channel — force the relay through the path that does publish, as the channel owner (unarchive is the one mutation permitted on an archived channel):
The second call publishes kind 39000 with ["archived", "true"] and every client converges. Note that buzz channels get does not surface archived state, so verification has to read the channel's latest kind 39000 tags.
A regression test could assert that after the last audio peer leaves, the newest kind 39000 for the huddle channel carries ["archived", "true"] — not just that channels.archived_at is set.
Describe the bug
When an audio room empties, the relay auto-archives the huddle's ephemeral backing channel by writing
channels.archived_atdirectly and emitting only kind48103. Unlike the two other archive paths, it never callsemit_group_discovery_events, so no kind39000carrying["archived", "true"]is ever published for that channel.Clients build their channel list from kind
39000, so the database and the event projection diverge permanently:<parent> huddlein the sidebar indefinitely.invalid: channel is archived), and deleting it hits Deleting an auto-archived huddle channel returns 400 "channel is archived" #2954. The channel cannot be removed by any client action.This does not self-heal. The ephemeral reaper is the one path that would publish the missing discovery update, but its
UPDATEfiltersch.archived_at IS NULL, so a channel already archived by the audio path is never picked up. The stale39000is a replaceable event, so the wrong state is what every future client syncs.I believe this is the root cause of the "Desktop can keep the channel row visible" behaviour described in #2954. That issue proposes letting kind
9008through for archived channels; this one is about clients never learning the archive happened in the first place. Fixing this removes the ghost row entirely, and the two fixes are complementary.Steps to reproduce
<parent> huddle(ttl_seconds = 3600).audio room empty — auto-ending huddle.SELECT archived_at FROM channels WHERE id = '<huddle channel>'→ set.39000for thatdtag → noarchivedtag.Compare with a huddle the owner ended normally: that one has a kind
9002and a follow-up39000with["archived", "true"].400 invalid: channel is archived. Delete channel → Deleting an auto-archived huddle channel returns 400 "channel is archived" #2954.Observed on a self-hosted single-relay deployment: of 16 huddle channels, the one that ended via the audio auto-end path was the only ghost; the 15 ended by the owner all converged correctly.
Expected behavior
The audio auto-end path should converge the event projection with the database exactly like the other two archive paths do — i.e. after a successful
archive_channel, emit the group discovery events (and, mirroring the reaper, likely also thechannel_auto_archivedsystem message andevict_all_channel_subscriptions).More generally:
archive_channelis currently called from three places, and only two of them publish the discovery update. Coupling the republish to the archive itself would make this class of divergence unrepresentable.Version and platform
ghcr.io/block/buzz:sha-8342dfc(commit8342dfcc5890b81a269a8ec3db73a8a56f76ce79), self-hosted, single relayLogs / additional context
Code paths at
8342dfcc5890b81a269a8ec3db73a8a56f76ce79:48103— no discovery republish:buzz/crates/buzz-relay/src/audio/handler.rs
Lines 833 to 860 in 8342dfc
emit_group_discovery_events, subscription eviction:buzz/crates/buzz-relay/src/main.rs
Lines 662 to 706 in 8342dfc
9002path archives and republishes discovery at the end ofhandle_edit_metadata:buzz/crates/buzz-relay/src/handlers/side_effects.rs
Lines 1573 to 1589 in 8342dfc
buzz/crates/buzz-relay/src/handlers/side_effects.rs
Line 1649 in 8342dfc
buzz/crates/buzz-db/src/channel.rs
Lines 1495 to 1506 in 8342dfc
archivedAtpurely from thearchivedtag on kind39000, which is why the stale event is decisive for the client:buzz/desktop/src-tauri/src/nostr_convert.rs
Lines 148 to 155 in 8342dfc
Workaround for an already-stuck channel — force the relay through the path that does publish, as the channel owner (unarchive is the one mutation permitted on an archived channel):
The second call publishes kind
39000with["archived", "true"]and every client converges. Note thatbuzz channels getdoes not surface archived state, so verification has to read the channel's latest kind39000tags.A regression test could assert that after the last audio peer leaves, the newest kind
39000for the huddle channel carries["archived", "true"]— not just thatchannels.archived_atis set.