Skip to content

fix(relay): republish group metadata when a huddle auto-archives - #4918

Open
Pratikkale26 wants to merge 1 commit into
block:mainfrom
Pratikkale26:fix/huddle-auto-archive-discovery-4879
Open

fix(relay): republish group metadata when a huddle auto-archives#4918
Pratikkale26 wants to merge 1 commit into
block:mainfrom
Pratikkale26:fix/huddle-auto-archive-discovery-4879

Conversation

@Pratikkale26

Copy link
Copy Markdown

Summary

When the last audio peer leaves a huddle, the relay archives the ephemeral backing channel in the database but never republishes kind:39000. Clients build their channel list from kind:39000, so the channel stays in the sidebar forever, on every client, including a fresh install — and no user action can remove it.

Fixes #4879.

The divergence, measured

Against a local relay on main, after the last audio peer drops:

# relay log
INFO audio room empty — auto-ending huddle channel_id=c773e368-…

# database — archived
$ select archived_at is not null from channels where id = 'c773e368-…';
 t

# newest kind:39000 for that channel — NOT archived
[["d","c773e368-…"],["name","huddle-c773e368-…"],["public"],["closed"],
 ["t","stream"],["ttl","3600"],["ttl_deadline","2026-08-05T17:25:13Z"]]
  ↑ no ["archived","true"]

The relay knows. No client ever will.

Root cause

crates/buzz-relay/src/audio/handler.rs — the auto-end path archives the channel and emits only kind:48103:

match state.db.archive_channel(tenant.community(), channel_id).await {
    Ok(()) => {
        room_emptied = state.audio_rooms.cleanup_if_empty(...);
        emit_participant_event(..., Kind::Custom(48103), ...).await;
        // never calls emit_group_discovery_events
    }
    ...
}

Every other archive path publishes the discovery update:

Path Republishes kind:39000?
Owner-driven kind:9002 (handlers/side_effects.rs:1649)
Ephemeral reaper (main.rs:685)
Huddle audio auto-end (audio/handler.rs)

This does not self-heal. The reaper is the one path that would republish, but reap_expired_ephemeral_channels filters archived_at IS NULL, so a row archived here is never picked up. And kind:39000 is replaceable, so the stale event is what every future client syncs.

The fix

After a successful archive_channel, mirror what the reaper already does:

  • emit_group_discovery_events — republishes kind:39000, which stamps ["archived","true"] from channel.archived_at. This is the actual fix.
  • evict_all_channel_subscriptions — drops live subscriptions so connected clients remove the channel immediately, rather than waiting for a reconnect.

Both are best-effort and logged on failure: a discovery hiccup must not change huddle teardown, which has already completed by this point.

One deliberate omission

The reaper also emits a channel_auto_archived system message. I left that out here: this path already emits kind:48103 (huddle ended), which is the semantically precise signal for a huddle, so a second "auto archived" notice would be redundant in the timeline. Happy to add it if maintainers prefer strict parity with the reaper — it's a one-line change.

On the broader suggestion in the issue

The issue notes that coupling the republish to the archive itself would make this class of divergence unrepresentable. I agree that's the better end state, but db.archive_channel lives in buzz-db, which has no access to relay state and cannot emit events, so it would need a relay-level wrapper adopted by all three paths. That's a refactor of two currently-working paths and felt out of scope for a bug fix — happy to follow up with it separately if wanted.

Testing

New crates/buzz-test-client/tests/e2e_huddle_archive.rs drives the real path end to end: create parent + ephemeral channel, publish the kind:48100 link, connect a peer to /huddle/{id}/audio, complete the NIP-42 handshake, wait for joined, then drop the socket and assert the newest kind:39000 carries ["archived","true"].

Validated by reverting the fix and re-running — it fails with:

assertion `left == right` failed: after the last audio peer leaves, the newest kind:39000
for the huddle channel must carry ["archived", "true"] — otherwise clients keep the channel forever
  left: Some(false)
 right: Some(true)
cargo fmt --all -- --check                              ✅
cargo clippy --workspace --all-targets -- -D warnings    ✅
just test                                                ✅
RELAY_URL=ws://localhost:3000 cargo test -p buzz-test-client \
  --test e2e_huddle_archive -- --ignored                 ✅

Related

When the last audio peer leaves, the relay archives the huddle's ephemeral
backing channel but never republishes kind:39000. Clients build their
channel list from kind:39000, so the database and the event projection
diverge permanently: the relay knows the channel is archived, while every
client — including a fresh install on a new machine — keeps showing the
huddle channel indefinitely.

Nothing repairs it afterwards. The ephemeral reaper is the one path that
would republish, but `reap_expired_ephemeral_channels` filters
`archived_at IS NULL`, so a row archived here is never picked up. And
kind:39000 is replaceable, so the stale event is what every future client
syncs. The channel also cannot be removed by any client action: archiving
it is refused by the archived-channel guard.

The two other archive paths already converge — the owner-driven kind:9002
handler and the reaper both call `emit_group_discovery_events`. Only the
audio auto-end path skipped it, emitting kind:48103 alone.

Mirror the reaper after a successful archive:

- `emit_group_discovery_events` republishes kind:39000, which stamps
  `["archived", "true"]` from `channel.archived_at`. This is the fix.
- `evict_all_channel_subscriptions` drops live subscriptions so connected
  clients remove the channel immediately rather than on next reconnect.

Both are best-effort and logged on failure: a discovery hiccup must not
change huddle teardown, which has already completed at this point.

The reaper's `channel_auto_archived` system message is deliberately not
mirrored — this path already emits kind:48103 (huddle ended), which is the
precise signal for a huddle, so a second notice would be redundant.

Adds an e2e test that drives the real path: create the parent and
ephemeral channels, publish the kind:48100 link, connect a peer to
`/huddle/{id}/audio`, complete the NIP-42 handshake, wait for `joined`,
drop the socket, then assert the newest kind:39000 carries
`["archived", "true"]`. It fails against the unfixed relay
(`left: Some(false)`) and passes with the fix.

Fixes block#4879

Signed-off-by: pratikkale26 <pratikkale7661@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Huddle auto-archive never republishes kind:39000, so the ephemeral channel stays live in every client forever

1 participant