affaan-m/ECC · error · AuthenticationError

{msg}

Error message

{msg}

What it means

ClaudeProvider.generate() wraps the Anthropic SDK; when an exception's message contains '429' or 'rate_limit' it is re-raised as RateLimitError tagged ProviderType.CLAUDE. Anthropic returns HTTP 429 with a rate_limit_error type, and the SDK message typically embeds the status code, so the substring match catches it. Claude also exposes cache_creation/cache_read token accounting (visible in the surrounding usage block), and prompt-caching misses are a common driver of input-token pressure that triggers the limit.

Source

Thrown at src/llm/providers/claude.py:128

            return LLMOutput(
                content="".join(text_parts),
                tool_calls=tool_calls or None,
                model=response.model,
                usage={
                    "input_tokens": response.usage.input_tokens,
                    "output_tokens": response.usage.output_tokens,
                    "cache_creation_input_tokens": getattr(
                        response.usage, "cache_creation_input_tokens", 0
                    ),
                    "cache_read_input_tokens": getattr(response.usage, "cache_read_input_tokens", 0),
                },
                stop_reason=response.stop_reason,
            )
        except Exception as e:
            msg = str(e)
            if "401" in msg or "authentication" in msg.lower():
                raise AuthenticationError(msg, provider=ProviderType.CLAUDE) from e
            if "429" in msg or "rate_limit" in msg.lower():
                raise RateLimitError(msg, provider=ProviderType.CLAUDE) from e
            if "context" in msg.lower() and "length" in msg.lower():
                raise ContextLengthError(msg, provider=ProviderType.CLAUDE) from e
            raise

    def list_models(self) -> list[ModelInfo]:
        return self._models.copy()

    def validate_config(self) -> bool:
        return bool(self.client.api_key)

    def get_default_model(self) -> str:
        return _DEFAULT_MODEL

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Wrap generate() with exponential backoff keyed on RateLimitError (respect any Retry-After the SDK exposes).
  2. Cap concurrency per Anthropic workspace/API key.
  3. Enable prompt caching (cache_creation_input_tokens) to cut billable input tokens.
  4. Move non-interactive workloads to the Anthropic batch API or upgrade the rate-limit tier.

Example fix

// before
output = provider.generate(llm_input)

// after
import time
from llm.core.interface import RateLimitError

for attempt in range(5):
    try:
        output = provider.generate(llm_input)
        break
    except RateLimitError:
        if attempt == 4:
            raise
        time.sleep(2 ** attempt)
Defensive patterns

Strategy: retry

Type guard

from llm.core.interface import RateLimitError

def is_rate_limit(exc: BaseException) -> bool:
    return isinstance(exc, RateLimitError)

Try / catch

import time
from llm.core.interface import RateLimitError

for attempt in range(5):
    try:
        output = provider.generate(llm_input)
        break
    except RateLimitError:
        if attempt == 4:
            raise
        time.sleep(2 ** attempt)

Prevention

When it happens

Trigger: Calling ClaudeProvider.generate() past Anthropic's per-minute ITPM/OTPM or requests-per-minute limit; the anthropic SDK raises an error whose str() contains '429' or 'rate_limit'.

Common situations: Bursty agentic loops firing many parallel Claude calls; exceeding the rolling 1-hour token bucket on a lower tier; a shared workspace key under multi-tenant load; prompt-caching misses inflating billable input tokens.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/83bbbc4284aaec9c. Report an issue: GitHub.