[Bot] Drop the unused privileged message_content intent and stop restart-looping on a missing Developer Portal toggle #394
Loading…
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?
Impact: HIGH
Found in the August 2026 session lifecycle review (#319).
What the user experiences
The bot requests the privileged
message_contentintent on startup but never uses it — there is noon_messagehandler and no prefix commands anywhere in the codebase. A customer who misses the corresponding toggle in the Discord Developer Portal getsPrivilegedIntentsRequired, which is not adiscord.LoginFailureand so is not caught by the bot's existing startup error handling — the process crashes and the Compose restart policy loops it indefinitely. The requirement to enable this toggle is documented only in a code comment, nowhere indocs/.Evidence
bot/questboard_bot/main.py:122-124—intents.message_content = True, a privileged intent; grep foron_messageand prefix-command usage inbot/finds none.bot/questboard_bot/main.py:407-421— the startup error handler catchesdiscord.LoginFailurebut notPrivilegedIntentsRequired, so the process crashes uncaught.bot/questboard_bot/config.py:461-463— the Developer Portal toggle requirement exists only as a code comment, not indocs/.Why it matters for a hosted product
This is a request for a permission the bot doesn't need, and the failure mode when a customer misses it is a silent crash-restart loop rather than a clear error — the worst possible combination for a self-hoster trying to diagnose why their bot won't start.
Proposed fix
Drop
intents.message_content = Truesince nothing in the codebase uses it. If it's needed for a planned future feature, keep it but catchPrivilegedIntentsRequiredexplicitly in the startup handler (main.py:417) and log an actionable message naming the Developer Portal toggle, instead of crash-looping. Document the requirement indocs/if the intent is kept. This is the audit's P30.Acceptance criteria
message_contentintent is removed, or a concrete planned use is documented alongside keeping it.PrivilegedIntentsRequiredis caught at startup with an actionable log message naming the missing toggle.docs/, not only in a code comment.Picking this up as part of v4.3.0 phase 1 (#514), shipping early as v4.2.3. Dropping the intent, since nothing reads message content, and catching
PrivilegedIntentsRequiredat startup anyway with a message that names the toggle, so a future intent cannot reintroduce the crash-loop.Fixed in PR #517 (merged), shipping in v4.2.3.
Confirmed by grep before removing anything: no
on_message, no@commands.commandorbot.command, no hybrid commands anywhere inbot/; all eight commands are@app_commands.command.command_prefix="!"remains only becausecommands.Botrequires the argument, and with the intent off it can never match. The intent is removed.PrivilegedIntentsRequiredis now caught anyway, for the next intent someone adds and for a portal toggle switched off under a running deployment. It is handled the wayLoginFailurealready was, for the same reason: both are configuration mistakes no amount of retrying fixes, so the gateway client stops while the aiohttp server keeps serving/health. Deliberately not a process exit:docker-compose.ymlsetsrestart: unless-stopped, which restarts on any exit code including 0, so exiting would crash-loop just as before, only more quietly. This way the container stays up and the log holds one line naming the exact Developer Portal toggle instead of a wall of identical tracebacks. That path moved into_run_discord_bot()so it can be tested without standing up the web app. The trade-off is that/healththen reports healthy while Discord is dead, which is the pre-existing trade-off forLoginFailuretoo; making the healthcheck reflect it would be a separate small issue.Docs corrected in four places because they were actively wrong:
.env.exampletold operators to enable Message Content and called Server Members "optional, for display names", which is backwards, since Server Members is what makes per-speaker recording attribution possible; alsoconfig.py's intents comment,docs/INTEGRATIONS.md's vague "enable the required intents", and a newdocs/OPERATIONS.mdtroubleshooting entry for the symptom.Four new tests: the constructed intents object has
message_contentFalse andmembersTrue;_run_discord_botturns bothPrivilegedIntentsRequiredandLoginFailureinto a logged clean stop naming the toggle; plus the success path. Self-hosters can now turn the Message Content toggle off in the Developer Portal; leaving it on is harmless.