Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agent/error_classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ def _classify_by_status(
return result_fn(FailoverReason.server_error, retryable=True)

if status_code in (503, 529):
return result_fn(FailoverReason.overloaded, retryable=True)
return result_fn(FailoverReason.overloaded, retryable=True, should_fallback=True)

# Other 4xx — non-retryable
if 400 <= status_code < 500:
Expand Down
15 changes: 15 additions & 0 deletions run_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -11144,6 +11144,21 @@ def _stop_spinner():
primary_recovery_attempted = False
continue

# Eager fallback for provider overload (503/529).
# Provider-side overload cannot be fixed by credential rotation,
# so bypass the pool check and switch to a fallback immediately.
# Fixes #11314 / #10210.
if (
classified.reason == FailoverReason.overloaded
and self._fallback_index < len(self._fallback_chain)
):
self._emit_status("⚠️ Provider overloaded — switching to fallback provider...")
if self._try_activate_fallback(reason=classified.reason):
retry_count = 0
compression_attempts = 0
primary_recovery_attempted = False
continue

# ── Nous Portal: record rate limit & skip retries ─────
# When Nous returns a 429, record the reset time to a
# shared file so ALL sessions (cron, gateway, auxiliary)
Expand Down
4 changes: 4 additions & 0 deletions tests/agent/test_error_classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,15 @@ def test_503_overloaded(self):
e = MockAPIError("Service Unavailable", status_code=503)
result = classify_api_error(e)
assert result.reason == FailoverReason.overloaded
assert result.should_fallback is True
assert result.retryable is True

def test_529_anthropic_overloaded(self):
e = MockAPIError("Overloaded", status_code=529)
result = classify_api_error(e)
assert result.reason == FailoverReason.overloaded
assert result.should_fallback is True
assert result.retryable is True

# ── Model not found ──

Expand Down
7 changes: 7 additions & 0 deletions tests/run_agent/test_provider_fallback.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
advancement through multiple providers.
"""

import sys
import types

# Prevent heavy imports from failing in lightweight test environments.
sys.modules.setdefault("fire", types.SimpleNamespace(Fire=lambda *a, **k: None))
sys.modules.setdefault("firecrawl", types.SimpleNamespace(Firecrawl=object))

from unittest.mock import MagicMock, patch

from run_agent import AIAgent, _pool_may_recover_from_rate_limit
Expand Down