Recover from SASL outcomes the library leaves hanging #7

Merged
drawblank merged 1 commit from fix/sasl-dead-ends into main 2026-08-20 11:10:59 +00:00
Owner

The bot connected, sat silent for 41 seconds, and was dropped by the server with nothing logged — with correct credentials.

11:04:05  Configured: nick=zuckerberg ... sasl=on sasl_account=zuckerberg
11:04:46  Disconnected from irc.slopnode.net: Connection reset by peer

Root cause

ServerConnection._sasl_auth_sent branches on saslsuccess (903) and saslfail (904) — and nothing else. Every other reply in the SASL block hits no branch:

902 nicklocked
905 sasltoolong
906 saslaborted
907 saslalready
908 saslmechs

_sasl_step stays set, CAP END is never sent, capability negotiation never ends, registration never completes, and the server times the connection out. Exactly the 41-second silence.

Verified against the real state machine with a fake socket:

Server replies 908 (saslmechs) -- PLAIN refused

  WITHOUT our handler:
    bot sent: (nothing)
    CAP END sent? False
    -> registration never completes; server times it out

  WITH our handler:
    SASL ended on saslmechs (908): PLAIN,EXTERNAL are available
    bot sent: ['CAP END']
    CAP END sent? True

Names come from the library

Event names are read from irc.events.numeric rather than copied, so they cannot drift from what the library dispatches, and handlers are registered by name through add_global_handler rather than as on_<name> methods.

Hardcoded here is only what is genuinely ours: that 900-908 is the SASL block, and which of those are handled upstream, informational, or dead ends.

Registration watchdog

If welcome has not arrived within 20 seconds, the bot now says so and names the two usual causes rather than sitting mute:

  • capability negotiation stuck part-way
  • the server expecting a PASS this client cannot send, since SASL and PASS share one password field

Verification

407 tests, up from 385. They pin the library's gap in both directions — that it still ignores these numerics, and that it still handles the two we defer to — so either changing upstream fails in CI rather than in production.

The bot connected, sat silent for 41 seconds, and was dropped by the server with **nothing logged** — with correct credentials. ``` 11:04:05 Configured: nick=zuckerberg ... sasl=on sasl_account=zuckerberg 11:04:46 Disconnected from irc.slopnode.net: Connection reset by peer ``` ## Root cause `ServerConnection._sasl_auth_sent` branches on `saslsuccess` (903) and `saslfail` (904) — and nothing else. Every other reply in the SASL block hits no branch: | | | |---|---| | 902 | nicklocked | | 905 | sasltoolong | | 906 | saslaborted | | 907 | saslalready | | 908 | saslmechs | `_sasl_step` stays set, **`CAP END` is never sent**, capability negotiation never ends, registration never completes, and the server times the connection out. Exactly the 41-second silence. Verified against the real state machine with a fake socket: ``` Server replies 908 (saslmechs) -- PLAIN refused WITHOUT our handler: bot sent: (nothing) CAP END sent? False -> registration never completes; server times it out WITH our handler: SASL ended on saslmechs (908): PLAIN,EXTERNAL are available bot sent: ['CAP END'] CAP END sent? True ``` ## Names come from the library Event names are read from `irc.events.numeric` rather than copied, so they cannot drift from what the library dispatches, and handlers are registered by name through `add_global_handler` rather than as `on_<name>` methods. Hardcoded here is only what is genuinely ours: that 900-908 is the SASL block, and which of those are handled upstream, informational, or dead ends. ## Registration watchdog If `welcome` has not arrived within 20 seconds, the bot now says so and names the two usual causes rather than sitting mute: - capability negotiation stuck part-way - the server expecting a `PASS` this client cannot send, since SASL and PASS share one password field ## Verification 407 tests, up from 385. They pin the library's gap **in both directions** — that it still ignores these numerics, and that it still handles the two we defer to — so either changing upstream fails in CI rather than in production.
The bot connected, sat silent for 41 seconds, and was dropped by the server
with nothing logged -- with correct credentials.

Root cause: ServerConnection._sasl_auth_sent branches on saslsuccess (903)
and saslfail (904) and nothing else. Any other reply in the SASL block --
902 nicklocked, 905 sasltoolong, 906 saslaborted, 907 saslalready,
908 saslmechs -- hits no branch, so _sasl_step stays set, CAP END is never
sent, capability negotiation never ends, registration never completes, and
the server times the connection out. Exactly the 41-second silence.

Verified against the real state machine: feeding it 908 produces no output
at all; with our handler it logs the reason and sends CAP END.

Event names are read from irc.events.numeric rather than copied, so they
cannot drift from what the library dispatches, and handlers are registered by
name via add_global_handler rather than as on_<name> methods. Hardcoded here
is only what is ours: that 900-908 is the SASL block, and which of those are
handled upstream, informational, or dead ends.

Also adds a registration watchdog. If welcome has not arrived in 20 seconds
the bot says so and names the two usual causes, rather than sitting mute --
capability negotiation stuck, or the server expecting a PASS this client
cannot send while SASL owns the password field.

Tests: 407, up from 385. They pin the library's gap in both directions: that
it still ignores these numerics, and that it still handles the two we defer
to, so either changing upstream fails in CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
slopnode/bot!7
No description provided.