Skip to content

fix(telegram): attach polling instrumentation without an instance attribute - #64639

Closed
roosy12 wants to merge 1 commit into
NousResearch:mainfrom
roosy12:fix/telegram-httpxrequest-slots
Closed

fix(telegram): attach polling instrumentation without an instance attribute#64639
roosy12 wants to merge 1 commit into
NousResearch:mainfrom
roosy12:fix/telegram-httpxrequest-slots

Conversation

@roosy12

@roosy12 roosy12 commented Jul 14, 2026

Copy link
Copy Markdown

Problem

Telegram cannot connect at all on v0.18.2. The adapter aborts on startup with:

AttributeError: 'HTTPXRequest' object attribute 'do_request' is read-only

Reported in #64566 and #64482.

Root cause

_instrument_polling_request() attaches its progress-tracking wrapper by assigning the attribute on the instance:

request.do_request = _do_request

HTTPXRequest declares __slots__ (python-telegram-bot >= 22), so the instance has no __dict__ and the assignment raises AttributeError.

Fix

Attach the wrapper by rebinding request.__class__ to a subclass that overrides do_request. A subclass with empty __slots__ has the same instance layout, so __class__ assignment is legal and creates no per-instance attribute. The instrumentation itself is unchanged.

Why the wrapper must not simply be skipped

The obvious fix — wrapping the assignment in try/except AttributeError and moving on — is wrong, and I tried it first. The polling-progress verifier consumes what _record_polling_progress() records. With a no-op wrapper, healthy polling looks stalled and the adapter drops into a reconnect loop:

Telegram polling degraded (polling progress verifier: general path healthy but getUpdates stalled)
Error: getUpdates made no progress before verifier deadline

So the wrapper has to actually attach, not merely fail quietly.

Testing

Added a regression test against the real HTTPXRequest:

  • Against unmodified main it fails with the exact production error (AttributeError ... is read-only at adapter.py:1999).
  • With this change it passes, and asserts progress is still recorded — so a future no-op regression is caught too.
  • tests/test_telegram_polling_progress_ptb.py goes from 7 passing to 8 passing when run on its own.

The existing request doubles subclass BaseRequest without __slots__, so they acquire a __dict__ and happily accept the assignment. That is why the suite never caught this — the doubles do not model the constraint that production hits.

Verified against a live bot: the gateway now connects, holds the getUpdates long-poll, and replies to messages.

Pre-existing issue noticed, unrelated to this change

Every test in tests/test_telegram_polling_progress_ptb.py fails when the file runs after tests/gateway/test_telegram_*.py in the same pytest session — this also happens on unmodified main (baseline: 7 failed / 68 passed; with this change: 8 failed / 68 passed, the extra failure being the new test succumbing to the same ordering issue). Run on its own the file is green. Flagging it as a separate test-isolation problem so it isn't mistaken for a regression from this PR.

Fixes #64566
Fixes #64482

🤖 Generated with Claude Code

…ribute

HTTPXRequest declares __slots__ (python-telegram-bot >= 22), so
_instrument_polling_request()'s `request.do_request = _do_request` raises

    AttributeError: 'HTTPXRequest' object attribute 'do_request' is read-only

and Telegram startup aborts. The adapter cannot connect at all.

Attach the wrapper by rebinding request.__class__ to a subclass that overrides
do_request instead. A subclass with empty __slots__ has the same instance
layout, so __class__ assignment is legal and creates no per-instance attribute.

The wrapper must not simply be skipped when it fails to attach: the polling
progress verifier consumes what _record_polling_progress() records, so a no-op
wrapper makes healthy polling look stalled and drops the adapter into a
reconnect loop ("getUpdates made no progress before verifier deadline").

Add a regression test against the real HTTPXRequest. The existing request
doubles subclass BaseRequest without __slots__, so they acquire a __dict__ and
accept the assignment — which is why the suite did not catch this.

Fixes NousResearch#64566
Fixes NousResearch#64482
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/plugins Plugin system and bundled plugins platform/telegram Telegram bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades duplicate This issue or pull request already exists labels Jul 14, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #64506 — same fix for the same regression (merged #64381's request.do_request instance assignment breaks on PTB's __slots__ed HTTPXRequest under Python 3.13, aborting Telegram startup with 'HTTPXRequest' object attribute 'do_request' is read-only). This PR uses the identical mechanism as #64506: re-tag request.__class__ to a __slots__=() subclass overriding do_request. #64506 (@HexLab98) is the earliest open PR with this approach and is the canonical fix; #64574 is a salvage of it. Flagging the three-way cluster (#64506 / #64574 / #64639) so a maintainer can pick one — all fix issues #64482 / #64566.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins duplicate This issue or pull request already exists P1 High — major feature broken, no workaround platform/telegram Telegram bot adapter sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

3 participants