{"record":{"id":"83bbbc4284aaec9c","repo":"affaan-m/ECC","slug":"msg-83bbbc","errorCode":null,"errorMessage":"{msg}","messagePattern":"\\{msg\\}","errorType":"exception","errorClass":"AuthenticationError","httpStatus":null,"severity":"error","filePath":"src/llm/providers/claude.py","lineNumber":128,"sourceCode":"\n            return LLMOutput(\n                content=\"\".join(text_parts),\n                tool_calls=tool_calls or None,\n                model=response.model,\n                usage={\n                    \"input_tokens\": response.usage.input_tokens,\n                    \"output_tokens\": response.usage.output_tokens,\n                    \"cache_creation_input_tokens\": getattr(\n                        response.usage, \"cache_creation_input_tokens\", 0\n                    ),\n                    \"cache_read_input_tokens\": getattr(response.usage, \"cache_read_input_tokens\", 0),\n                },\n                stop_reason=response.stop_reason,\n            )\n        except Exception as e:\n            msg = str(e)\n            if \"401\" in msg or \"authentication\" in msg.lower():\n                raise AuthenticationError(msg, provider=ProviderType.CLAUDE) from e\n            if \"429\" in msg or \"rate_limit\" in msg.lower():\n                raise RateLimitError(msg, provider=ProviderType.CLAUDE) from e\n            if \"context\" in msg.lower() and \"length\" in msg.lower():\n                raise ContextLengthError(msg, provider=ProviderType.CLAUDE) from e\n            raise\n\n    def list_models(self) -> list[ModelInfo]:\n        return self._models.copy()\n\n    def validate_config(self) -> bool:\n        return bool(self.client.api_key)\n\n    def get_default_model(self) -> str:\n        return _DEFAULT_MODEL\n","sourceCodeStart":110,"sourceCodeEnd":143,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/src/llm/providers/claude.py#L110-L143","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Confirm ANTHROPIC_API_KEY is set and non-empty in the runtime environment (check length/prefix only, never log it).","Regenerate the key in the Anthropic console if it was rotated or revoked, and redeploy the secret.","Validate the key with a direct curl call to the messages endpoint to isolate library vs credential issues.","Ensure the key format and auth header match the API version the SDK expects (x-api-key, not Bearer)."],"exampleFix":"// before\nprovider = ClaudeProvider()  # relies on missing ANTHROPIC_API_KEY\n\n// after\nkey = os.getenv(\"ANTHROPIC_API_KEY\", \"\").strip()\nif not key.startswith(\"sk-ant-\"):\n    raise RuntimeError(\"ANTHROPIC_API_KEY missing or malformed\")\nprovider = ClaudeProvider(api_key=key)","handlingStrategy":"try-catch","validationCode":"key = os.getenv(\"ANTHROPIC_API_KEY\", \"\").strip()\nif not key.startswith(\"sk-ant-\"):\n    raise RuntimeError(\"ANTHROPIC_API_KEY missing or malformed before calling generate()\")","typeGuard":null,"tryCatchPattern":"try:\n    out = provider.generate(prompt)\nexcept AuthenticationError as e:\n    log.error(\"claude auth rejected: %s\", e)\n    rotate_credentials_and_retry_once()","preventionTips":["Validate the key's presence and format at process startup, not on first request.","Store the key in a secret manager and re-inject after rotations.","Smoke-test credentials with a minimal API call in deploy pipelines."],"tags":["authentication","api-key","claude","http-401"],"backgroundTag":"upstream-api-error","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}