Permissions are pre-checked now, so a connect timeout is the voice handshake
(pre-2.7 discord.py: DAVE close 4017 retried to the 30s timeout) or UDP egress,
not permissions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live symptom: the radio bot shows "Listening to <system>" but is not in the
voice channel.
- Presence was set once on the first successful join and never cleared, so a
bot whose voice dropped (or whose rejoin kept failing) still looked healthy.
Presence is now synced at the end of every reconcile: set only while a voice
connection for that bot is up, cleared otherwise.
- Pre-check the bot's permissions (View Channel, Connect, Speak) and the
channel's user limit before joining. Discord silently ignores a join the bot
may not make; discord.py then times out after 30s and the admin page read
"Could not connect to the voice channel (TimeoutError: )". Now: code
missing_permissions / channel_full with the missing permission named.
- A connect timeout gets its own code (connect_timeout) and readable text.
- requirements: discord.py[voice]>=2.7 — Discord has required DAVE E2EE voice
since 2026-03-01 and 2.7 is the first discord.py with it (the extra pulls
davey). Today's resolution was already 2.7.1 + davey 0.1.6; this stops an
older resolution from ever being accepted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>