diff --git a/agent/retry_utils.py b/agent/retry_utils.py index a5e304cadb..ab28ee37a9 100644 --- a/agent/retry_utils.py +++ b/agent/retry_utils.py @@ -88,10 +88,13 @@ def _resets_in_seconds(m: "re.Match[str]") -> Optional[float]: return float(m.group(1) or 0) * 3600 + float(m.group(2) or 0) * 60 + float(m.group(3) or 0) +# An explicit "retry after N s" wins over "resets in ..." (the credential pool's precedence): +# a body carrying both describes a short throttle inside a long quota window, and the +# shorter explicit wait is the one the provider actually asks for. RETRY_DELAY_PATTERNS = ( (_QUOTA_RESET_DELAY_RE, _quota_reset_seconds), - (_RESETS_IN_RE, _resets_in_seconds), (_RETRY_AFTER_SECONDS_RE, lambda m: float(m.group(1))), + (_RESETS_IN_RE, _resets_in_seconds), ) diff --git a/tests/agent/test_retry_delay_parsers_shared.py b/tests/agent/test_retry_delay_parsers_shared.py index 1f4f7db293..a414821a80 100644 --- a/tests/agent/test_retry_delay_parsers_shared.py +++ b/tests/agent/test_retry_delay_parsers_shared.py @@ -57,6 +57,9 @@ class TestResetDelayOneTable: ("Limit hit; resets in 45s", 45.0), ('"quotaResetDelay": "1500ms"', 1.5), ("please retry after 12 seconds", 12.0), + # Both grammars in one body: the explicit retry-after wins (pool precedence), not the + # multi-hour quota window. + ("Rate limited. Retry after 30s; resets in 4hr", 30.0), ]) def test_credential_pool_and_error_context_agree(self, message, seconds): """The pooled-credential cooldown and the UI's error context read the same table, so the