Matrix call-end teardown overrides the capture-only-anchor grace — bridge dormant with Discord humans still in channel #59
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
dark/nether-voicebridge#59
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Matrix call-end teardown overrides the capture-only-anchor grace — bridge goes dormant with Discord humans still in channel
Observed twice on prod (v0.3.0-alpha.7) during the #46 cross-guild test session, 2026-07-05 UTC.
What happens
When the last Matrix call member leaves while Discord humans remain in the bridged channel, the puppet layer decides to keep a capture-only anchor — and then the Matrix call-end teardown releases it anyway, ~0.5–0.7 s later:
Identical sequence again at
18:02:04.451→18:02:05.108(same bridge, same session — fully reproducible).At 16:56 the channel still had 2 human members (
voicetick ... silent=2right up to the teardown). The bridge then sat dormant for ~36 minutes with humans in the channel: their idle presence generates no gateway events, so nothing re-activates the bridge until a NEW voice-state event arrives (a user joining at 17:32 re-minted presence ghosts; anchor voice-join additionally required a Matrix user joining the call — by design).Why it matters
keeping capture-only anchor (grace before release)path appears to be dead code in practice — whatever grace it intends never survives thecall endedteardown that runs in the same instant.seeding ghosts for existing voice members count=3, which suggests they had to be rebuilt — i.e. between call-end and the next voice-state event, Discord users idling in the channel may be invisible in the Matrix room (no ghosts posted), so a Matrix user opening the room sees an empty call instead of the waiting Discord participants.Where to look
crates/discord/src/puppet.rs— the "last puppet released but Discord speakers remain — keeping capture-only anchor" site vs. the release path that wins.call ended (after grace)/call ended — disconnecting subscriberteardown incrates/matrix-rtc— whether it unconditionally releases the channel's puppets.Fixed: merged to master
5f7587d(rides v0.3.0-alpha.11).Root cause confirmed exactly as filed: the pool's
releasecorrectly kept a capture-only anchor when Discord speakers remained, but the call-end teardown (call_active=false→teardown(release_routes=true)→release_all) freed it unconditionally ~0.5 s later —release_all_lockednever consultedspeakers_presentand explicitly released the capture-only-held anchor (its "return to baseline" branch).Fix (keep-anchor design):
release_all_lockedgained akeep_capture_anchormode — the call-end path now mirrorsrelease's keep semantics (anchor demoted to capture-only, playback identity cleared,ANCHOR_IDLE_GRACEarmed), whileunregister_channel(process/tenant drain) still drains unconditionally, so teardown-to-baseline is preserved where it matters. A Matrix user returning within the 90 s grace reuses the live session instantly (no join latency, no SSRC-map reset). The idling anchor wears a configurable standby label —[puppets] standby_nickname, default"[Matrix: Standby]", a whole nickname rather than the prefix path — so it doesn't read as a participant named after the bare prefix.Live-verified on staging 2026-07-07: the exact prod sequence (leave grace → keep decision → call-end 34 ms later) now logs "call ended but Discord speakers remain — keeping capture-only anchor" instead of releasing; EC rejoin within grace reused the anchor with instant audio; grace expiry released to baseline at +90 s on the dot; standby label confirmed visually. Unit tests cover keep-on-call-end, demote-active-anchor-free-the-rest, drain-without-speakers, and the unregister path (236 tests + clippy green).
Note: the open question about presence ghosts between call-end and the next voice event was NOT addressed here (out of scope); it remains observable-but-unfixed if it reproduces.