Summary:
The Claude provider's retry logic silently skips retries for HTTP 429 (rate limit) and 5xx errors when using the Pydantic AI backend.
Agents fail immediately even with retry configured in workflow YAML.
Environment:
Conductor v0.1.31
pydantic-ai 2.x(latest)
Provider: claude (Pydantic AI backend)
Steps to Reproduce:
1.Configure an agent with retry
agents:
-name: my_agent
retry:
max_attempts: 3
backoff: exponential
delay_seconds: 60
retry_on: [provider_error]
- Hit a 429 rate limit from the API.
- Observe: agent fails immediately. Zero agent_retry events in the event log.
Root Cause:
Pydantic AI v2 wraps HTTP errors as pydantic_ai.exceptions.ModelHTTPError. In execute_with_retry(), the function _is_retryable_error() checks for:
- conductor.exceptions.ProviderError -- ModelHTTPError is not this
- Anthropic SDK class names (RateLimitError, APIStatusError) -- "ModelHTTPError" is not in the set
- anthropic.APIStatusError isinstance check -- ModelHTTPError is not this
All checks fail -> returns False -> error is immediately re-raised at L289 as ProviderError(is_retryable=False)
, before the retry loop ever executes.
Suggested Fix:
Add ModelHTTPError handling:
try:
from pydantic_ai.exceptions import ModelHTTPError
except ImportError:
ModelHTTPError = None
def _is_retryable_error(exception):
# New: handle Pydantic AI's HTTP error type
if ModelHTTPError is not None and isinstance(exception, ModelHTTPError):
code = exception.status_code
return code == 429 or 500 <= code < 600
Also worth considering:
_get_retry_after() should extract the wait time from ModelHTTPError.body when present, so Conductor can respect the server's requested delay instead of falling back to _calculate_delay().
Related:
max_delay default silently caps delay_seconds
A secondary issue compounds the problem. RetryConfig.max_delay defaults to 30 seconds.
When a user sets delay_seconds: 60, the delay is silently capped:
delay = base_delay * 2 ** (attempt - 1) # 60 * 1 = 60
delay = min(delay, config.max_delay) # min(60, 30) = 30 <- capped
For rate limits with a 60second window, the 30second wait is insufficient, causing repeated 429s even when retries do fire. The user's explicit delay_seconds should not be overridden by a lower default.
Suggestion:
Ensure max_delay >= delay_seconds when resolving the config.
Summary:
The Claude provider's retry logic silently skips retries for HTTP 429 (rate limit) and 5xx errors when using the Pydantic AI backend.
Agents fail immediately even with retry configured in workflow YAML.
Environment:
Conductor v0.1.31
pydantic-ai 2.x(latest)
Provider: claude (Pydantic AI backend)
Steps to Reproduce:
1.Configure an agent with retry
Root Cause:
Pydantic AI v2 wraps HTTP errors as pydantic_ai.exceptions.ModelHTTPError. In execute_with_retry(), the function _is_retryable_error() checks for:
All checks fail -> returns False -> error is immediately re-raised at L289 as ProviderError(is_retryable=False)
, before the retry loop ever executes.
Suggested Fix:
Add ModelHTTPError handling:
Also worth considering:
_get_retry_after() should extract the wait time from ModelHTTPError.body when present, so Conductor can respect the server's requested delay instead of falling back to _calculate_delay().
Related:
max_delay default silently caps delay_seconds
A secondary issue compounds the problem. RetryConfig.max_delay defaults to 30 seconds.
When a user sets delay_seconds: 60, the delay is silently capped:
For rate limits with a 60second window, the 30second wait is insufficient, causing repeated 429s even when retries do fire. The user's explicit delay_seconds should not be overridden by a lower default.
Suggestion:
Ensure max_delay >= delay_seconds when resolving the config.