discord_radio: leave keeps the gateway session alive after C2 releases the token; watchdog rejoin cancels itself; @bot leave has no auth #25

Closed
opened 2026-10-04 15:34:20 -04:00 by logan · 2 comments
Owner

Found during the Discord system design audit (2026-10-04, design-canvas/discord-system-notes.md). Touches discord_radio.py — run drb-audio-review before committing a fix.

Client/drb-edge-node/app/internal/discord_radio.py:

  1. Two nodes on one bot token. leave() (L72-88) disconnects voice but leaves the bot logged in to the gateway. C2 has already marked the token free (server routers/nodes.py:122), so the next discord_join anywhere hands the same token to another node. Both nodes now run a session on that token: both answer @bot joinme (L254-274) and fight over voice in the same guild. Same outcome after the C2 offline sweeper releases a token for a node that lost MQTT but kept internet. Fix: leave() should stop() the bot when it was the last voice connection (or C2 should send a distinct discord_release).
  2. Watchdog rejoin self-cancels. When the bot task has died (_is_bot_running() false), the watchdog (L193) calls join() -> _start_bot() -> stop() (L209), which cancels self._watchdog_task — the task currently running — and clears _guild_id/_channel_id. The await self._bot.close() then raises CancelledError into the watchdog, which returns (L203). Result: no recovery ever, token still in_use at C2. The rejoin path also drops system_name, so presence is lost after any rejoin.
  3. @bot leave / @bot join from anyone. on_message (L242-274) acts on a substring match by any guild member: "@bot don't leave" disconnects it. No role check, no rate limit.
  4. Local dashboard Discord join is broken. routers/api.py:355-360 calls radio_bot.join(guild_id, channel_id) without the required token arg -> TypeError -> 500 every time.
  5. No join/leave result is ever published back; C2 and the Discord bot learn about failures only through discord_connected in the next checkin.

DEFERRED.md L18 (flake8 in this file) and L21 (sync pulse probe) are related but separate.

Found during the Discord system design audit (2026-10-04, `design-canvas/discord-system-notes.md`). Touches `discord_radio.py` — run `drb-audio-review` before committing a fix. `Client/drb-edge-node/app/internal/discord_radio.py`: 1. **Two nodes on one bot token.** `leave()` (L72-88) disconnects voice but leaves the bot logged in to the gateway. C2 has already marked the token free (`server routers/nodes.py:122`), so the next `discord_join` anywhere hands the same token to another node. Both nodes now run a session on that token: both answer `@bot joinme` (L254-274) and fight over voice in the same guild. Same outcome after the C2 offline sweeper releases a token for a node that lost MQTT but kept internet. Fix: `leave()` should `stop()` the bot when it was the last voice connection (or C2 should send a distinct `discord_release`). 2. **Watchdog rejoin self-cancels.** When the bot task has died (`_is_bot_running()` false), the watchdog (L193) calls `join()` -> `_start_bot()` -> `stop()` (L209), which cancels `self._watchdog_task` — the task currently running — and clears `_guild_id/_channel_id`. The `await self._bot.close()` then raises CancelledError into the watchdog, which returns (L203). Result: no recovery ever, token still `in_use` at C2. The rejoin path also drops `system_name`, so presence is lost after any rejoin. 3. **`@bot leave` / `@bot join` from anyone.** `on_message` (L242-274) acts on a substring match by any guild member: "@bot don't leave" disconnects it. No role check, no rate limit. 4. **Local dashboard Discord join is broken.** `routers/api.py:355-360` calls `radio_bot.join(guild_id, channel_id)` without the required `token` arg -> TypeError -> 500 every time. 5. No join/leave result is ever published back; C2 and the Discord bot learn about failures only through `discord_connected` in the next checkin. DEFERRED.md L18 (flake8 in this file) and L21 (sync pulse probe) are related but separate.
Author
Owner

PR: logan/server-26#216 (server) + #32 (edge). Merge both together. Follow-ups filed: server-26#213 (guild link UI/admin page), server-26#214 (ad-hoc idle dismiss). drb-audio-review still to run on the edge PR before merge.

PR: https://git.vpn.cusano.net/logan/server-26/pulls/216 (server) + https://git.vpn.cusano.net/logan/node-26/pulls/32 (edge). Merge both together. Follow-ups filed: server-26#213 (guild link UI/admin page), server-26#214 (ad-hoc idle dismiss). drb-audio-review still to run on the edge PR before merge.
logan added the area/discord
kind
behavior-bug
priority
p2
type
bug
labels 2026-10-05 02:05:11 -04:00
Author
Owner

Fixed by PR #32 (merge 8c0bf75). It's in release v1 and installed on the nodes.

Fixed by PR #32 (merge 8c0bf75). It's in release v1 and installed on the nodes.
logan closed this issue 2026-10-05 02:08:57 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/node-26#25