affaan-m/ECC · error · AuthenticationError

{msg}

Error message

{msg}

What it means

ClaudeProvider.generate() translates upstream API exceptions into typed errors; a message containing '401' or 'authentication' is re-raised as AuthenticationError with ProviderType.CLAUDE. This means the Anthropic-compatible API rejected the request's credentials before any completion was produced. The original SDK exception is chained via `from e` for diagnosis.

Solutions

  1. Confirm ANTHROPIC_API_KEY is set and non-empty in the runtime environment (check length/prefix only, never log it).
  2. Regenerate the key in the Anthropic console if it was rotated or revoked, and redeploy the secret.
  3. Validate the key with a direct curl call to the messages endpoint to isolate library vs credential issues.
  4. Ensure the key format and auth header match the API version the SDK expects (x-api-key, not Bearer).

Example fix

// before
provider = ClaudeProvider()  # relies on missing ANTHROPIC_API_KEY

// after
key = os.getenv("ANTHROPIC_API_KEY", "").strip()
if not key.startswith("sk-ant-"):
    raise RuntimeError("ANTHROPIC_API_KEY missing or malformed")
provider = ClaudeProvider(api_key=key)
Defensive patterns

Strategy: try-catch

Validate before calling

key = os.getenv("ANTHROPIC_API_KEY", "").strip()
if not key.startswith("sk-ant-"):
    raise RuntimeError("ANTHROPIC_API_KEY missing or malformed before calling generate()")

Try / catch

try:
    out = provider.generate(prompt)
except AuthenticationError as e:
    log.error("claude auth rejected: %s", e)
    rotate_credentials_and_retry_once()

Prevention

When it happens

Trigger: Calling generate() when the Claude API returns 401 Unauthorized — invalid/missing ANTHROPIC_API_KEY, malformed x-api-key header, revoked or expired key, or key belonging to a different organization than the requested workspace.

Common situations: ANTHROPIC_API_KEY unset in the deployment environment; key rotated and the old one still cached; copy-paste dropped characters from the key; CI secrets not injected; using an OpenAI-format key against the Claude endpoint.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/83bbbc4284aaec9c. Report an issue: GitHub.

Appendix: 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 8321021c54)