Huddle auto-archive never republishes kind:39000, so the ephemeral channel stays live in every client forever
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
**Describe the bug**
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 ` huddle` in the sidebar indefinitely.
- Archiving it from the UI is rejected by the archived-channel guard (`invalid: channel is archived`), and deleting it hits #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 `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**
1. Start a huddle in a channel. The relay creates the ephemeral backing channel ` huddle` (`ttl_seconds = 3600`).
2. 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`.
3. Inspect relay state:
- `SELECT archived_at FROM channels WHERE id = ''` → 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"]`.
4. The channel stays in the sidebar forever. Right-click → **Archive channel** → `400 invalid: channel is archived`. **Delete channel** → #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 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: https://github.com/block/buzz/blob/8342dfcc5890b81a269a8ec3db73a8a56f76ce79/crates/buzz-relay/src/audio/handler.rs#L833-L860
- The reaper does it correctly — system message, `emit_group_discovery_events`, subscription eviction: https://github.com/block/buzz/blob/8342dfcc5890b81a269a8ec3db73a8a56f76ce79/crates/buzz-relay/src/main.rs#L662-L706
- The owner-driven kind `9002` path archives and republishes discovery at the end of `handle_edit_metadata`: https://github.com/block/buzz/blob/8342dfcc5890b81a269a8ec3db73a8a56f76ce79/crates/buzz-relay/src/handlers/side_effects.rs#L1573-L1589 and https://github.com/block/buzz/blob/8342dfcc5890b81a269a8ec3db73a8a56f76ce79/crates/buzz-relay/src/handlers/side_effects.rs#L1649
- The reaper skips already-archived rows, so it cannot repair the divergence: https://github.com/block/buzz/blob/8342dfcc5890b81a269a8ec3db73a8a56f76ce79/crates/buzz-db/src/channel.rs#L1495-L1506
- Desktop derives `archivedAt` purely from the `archived` tag on kind `39000`, which is why the stale event is decisive for the client: https://github.com/block/buzz/blob/8342dfcc5890b81a269a8ec3db73a8a56f76ce79/desktop/src-tauri/src/nostr_convert.rs#L148-L155
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):
```bash
buzz channels unarchive --channel
buzz channels archive --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.
Contributor guide
Assessment
This issue has not been assessed yet.