From d1facd8bdce0f7966a4161957032f12cd5f37b59 Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Sun, 19 Jul 2026 21:20:10 -0700 Subject: [PATCH] =?UTF-8?q?fix(skills):=20TG=20poll=205xx=20triggers=20net?= =?UTF-8?q?work=20backoff=20=E2=80=94=20HTTPError=20>=3D500=20in=20poll=5F?= =?UTF-8?q?updates=20raises=20=5FNetworkPollError=20instead=20of=20rapid-f?= =?UTF-8?q?ire=20retry;=204xx=20unchanged.=203=20new=20tests=20(502/503=20?= =?UTF-8?q?backoff,=20429=20excluded).=20Medic=20loop:=20detected=20by=20@?= =?UTF-8?q?trigger=20(ERROR=20ae5ddbd4),=20fixed=20autonomously=20by=20@sk?= =?UTF-8?q?ills,=20verified=20by=20devpulse=20(1080=20tests=20green,=20see?= =?UTF-8?q?dgo=20100%)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 ++++ .../lib/telegram/apps/handlers/base_bot.py | 2 ++ .../telegram/tests/test_network_backoff.py | 30 ++++++++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3072e334..f041f713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,11 @@ PyPI version — not the changelog header. was already covered via the shared dispatch_monitor wrapper. @daemon's scheduled wakes import `wake_branch` directly and were covered from the start. +**fix(skills)** — Telegram poll 5xx now triggers network backoff (medic loop, +autonomous @skills fix): `HTTPError` ≥500 in `poll_updates` raises +`_NetworkPollError` instead of falling through to rapid-fire retry; 4xx still +logs and returns. Three new tests (502/503 backoff, 429 stays out). + **fix(spawn, commons, prax, hooks)** — S304 audit fix campaign, Track A (DPLAN-0250, four owner dispatches verified + committed by devpulse): diff --git a/src/aipass/skills/lib/telegram/apps/handlers/base_bot.py b/src/aipass/skills/lib/telegram/apps/handlers/base_bot.py index 99cee968..5abe5cbb 100644 --- a/src/aipass/skills/lib/telegram/apps/handlers/base_bot.py +++ b/src/aipass/skills/lib/telegram/apps/handlers/base_bot.py @@ -468,6 +468,8 @@ class BaseBot: return [] if _is_network_error(e): raise _NetworkPollError(str(e)) from e + if isinstance(e, HTTPError) and e.code >= 500: + raise _NetworkPollError(str(e)) from e logger.error("Poll error: %s", e) return [] except (ConnectionError, OSError) as e: diff --git a/src/aipass/skills/lib/telegram/tests/test_network_backoff.py b/src/aipass/skills/lib/telegram/tests/test_network_backoff.py index cb67b082..07af0161 100644 --- a/src/aipass/skills/lib/telegram/tests/test_network_backoff.py +++ b/src/aipass/skills/lib/telegram/tests/test_network_backoff.py @@ -14,8 +14,9 @@ Tests cover: - _is_routine_read_timeout classification """ +from http.client import HTTPMessage from unittest.mock import patch -from urllib.error import URLError +from urllib.error import HTTPError, URLError import pytest @@ -206,6 +207,33 @@ class TestPollUpdatesErrorClassification: mock_logger.error.assert_called_once() assert "Poll error" in str(mock_logger.error.call_args) + def test_http_502_raises_network_poll_error(self, tmp_path, _patch_base_bot_deps): + """HTTP 502 Bad Gateway should trigger network backoff, not rapid-fire retry.""" + bot = _make_bot(tmp_path, _patch_base_bot_deps) + exc = HTTPError("https://api.telegram.org/...", 502, "Bad Gateway", HTTPMessage(), None) + with patch("aipass.skills.lib.telegram.apps.handlers.base_bot.urlopen", side_effect=exc): + with pytest.raises(_NetworkPollError): + bot.poll_updates(0) + + def test_http_503_raises_network_poll_error(self, tmp_path, _patch_base_bot_deps): + bot = _make_bot(tmp_path, _patch_base_bot_deps) + exc = HTTPError("https://api.telegram.org/...", 503, "Service Unavailable", HTTPMessage(), None) + with patch("aipass.skills.lib.telegram.apps.handlers.base_bot.urlopen", side_effect=exc): + with pytest.raises(_NetworkPollError): + bot.poll_updates(0) + + def test_http_429_not_network_error(self, tmp_path, _patch_base_bot_deps): + """Client-side HTTP errors (4xx) should NOT trigger network backoff.""" + bot = _make_bot(tmp_path, _patch_base_bot_deps) + exc = HTTPError("https://api.telegram.org/...", 429, "Too Many Requests", HTTPMessage(), None) + with ( + patch("aipass.skills.lib.telegram.apps.handlers.base_bot.urlopen", side_effect=exc), + patch("aipass.skills.lib.telegram.apps.handlers.base_bot.logger") as mock_logger, + ): + result = bot.poll_updates(0) + assert result == [] + mock_logger.error.assert_called_once() + def test_unexpected_exception_logs_error(self, tmp_path, _patch_base_bot_deps): bot = _make_bot(tmp_path, _patch_base_bot_deps) exc = ValueError("something weird")