fix(discord): treat a rejected bot token as non-retryable - #68158
fix(discord): treat a rejected bot token as non-retryable#68158FixItFoundry wants to merge 1 commit into
Conversation
connect() has no handler for discord.LoginFailure, so a rejected token falls through to the generic `except Exception`, which never calls _set_fatal_error. _fatal_error_retryable therefore keeps its default of True and GatewayRunner re-attempts the connect on the reconnect backoff forever -- each attempt another failed identify, which Discord penalizes. Rotating or revoking a bot token is enough to trigger it, and the loop never resolves because retrying cannot fix an auth rejection. Catch LoginFailure ahead of the generic handler and record it with retryable=False, so the runner drops the platform and surfaces a clear "disabled" state instead of retrying indefinitely. The handler is specific, not broad, so the generic branch below stays reachable and transient faults (DNS, proxy, network blips) remain retryable. The shared gateway conftest gains a LoginFailure stub as a distinct Exception subclass rather than an alias of Exception, so the two handlers stay distinguishable under test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the focused Discord failure classification. The premise holds on current main: The proposed Automated hermes-sweeper review. |
|
Closing with credit: you were the first submitter on the Discord LoginFailure → non-retryable classification (July 20), three weeks ahead of the broader fix that just landed. PR #85049 (#85049, salvaged from #83713) merged the same discord auth classification as part of a four-platform sweep (telegram/discord/photon/email) plus a needs_attention escalation for long-lived retry loops, so this PR's change is now on main via Your test approach (real LoginFailure subclass in the conftest mock so the generic handler stays reachable) was solid — the merged suite covers the same branches. Thanks for spotting this early and sorry the credit lands via a close instead of a merge. |
The bug
DiscordAdapter.connect()has no handler fordiscord.LoginFailure, so a rejected token falls through to the genericexcept Exceptionat the end of the method. That branch logs and returnsFalsebut never calls_set_fatal_error, so_fatal_error_retryablekeeps its constructor default ofTrue(gateway/platforms/base.py:2449).GatewayRunnerreads that flag and queues the platform for background reconnection when it's true. The result: an auth rejection is retried on the reconnect backoff (30s, 60s, 120s, … capped at 5 min) forever. Retrying cannot fix it — the token is wrong — and every attempt is another failed identify, which Discord penalizes.Rotating or revoking a bot token is enough to trigger this.
The fix
Catch
LoginFailureahead of the generic handler and record it withretryable=False, so the runner drops the platform and renders a clear disabled state instead of looping.The handler is deliberately specific, not broad, so the generic
except Exceptionbelow stays reachable — transient faults (DNS, proxy, network blips) must remain retryable. There's a regression test pinning exactly that.Test-stub note
The shared gateway conftest gains a
LoginFailurestub. It's a distinctExceptionsubclass rather than an alias ofException, because aliasing the two would make the generic handler unreachable under test and quietly void the second test below. This mirrors how the same file already stubs PTB's error hierarchy.Tests
test_connect_marks_login_failure_non_retryable— assertsfatal_error_code == "discord_auth_failed",fatal_error_retryable is False, and that the token lock is still released. Verified to fail when the new handler is removed.test_connect_generic_failure_stays_retryable— aRuntimeErrorfrom the same path must stay retryable and must not be recorded as an auth failure. This is the guard against the new handler shadowing the existing one.tests/gateway/test_discord_connect.pypasses; the widertests/gatewayrun shows no regressions against the upstream baseline.