{"record":{"id":"41ce8ae07a9d0288","repo":"BerriAI/litellm","slug":"str-e-41ce8a","errorCode":null,"errorMessage":"str(e)","messagePattern":"str\\(e\\)","errorType":"exception","errorClass":"AuthenticationError","httpStatus":401,"severity":"critical","filePath":"litellm/llms/github_copilot/messages/transformation.py","lineNumber":75,"sourceCode":"        api_key: str | None = None,\n        api_base: str | None = None,\n    ) -> tuple[dict, str | None]:\n        \"\"\"\n        Validate environment for GitHub Copilot and add Copilot-specific headers.\n\n        The caller-supplied ``api_base`` is intentionally ignored. Routing this\n        request anywhere other than the authenticated Copilot endpoint would\n        leak the Copilot bearer token to a caller-controlled URL.\n        \"\"\"\n        # Always use the Copilot endpoint resolved from the authenticated\n        # session, never the caller-supplied api_base. rstrip so a\n        # tenant-specific base with a trailing slash does not yield a\n        # double-slash URL once \"/v1/messages\" is appended downstream.\n        dynamic_api_base: Final = (self.authenticator.get_api_base() or DEFAULT_GITHUB_COPILOT_API_BASE).rstrip(\"/\")\n        try:\n            dynamic_api_key: Final = self.authenticator.get_api_key()\n        except GetAPIKeyError as e:\n            raise AuthenticationError(\n                model=model,\n                llm_provider=\"github_copilot\",\n                message=str(e),\n            )\n\n        # Merge Copilot headers with provided headers\n        copilot_headers: Final = get_copilot_default_headers(dynamic_api_key)\n        for key, value in copilot_headers.items():\n            if key not in headers:\n                headers[key] = value\n\n        headers[\"openai-intent\"] = \"messages-proxy\"\n        headers[\"x-interaction-type\"] = \"messages-proxy\"\n        headers[\"x-github-api-version\"] = _MESSAGES_PROXY_API_VERSION\n\n        if \"anthropic-version\" not in headers:\n            headers[\"anthropic-version\"] = \"2023-06-01\"\n","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/github_copilot/messages/transformation.py#L57-L93","documentation":"Raised in the messages (Anthropic-style) transformation when self.authenticator.get_api_key() throws GetAPIKeyError; wrapped as AuthenticationError with str(e). Same wrapper family as errors 1630/1632 but for the /v1/messages proxy path. Note this path deliberately ignores caller-supplied api_base to avoid leaking the Copilot bearer token, so the failure is purely about the OAuth key pipeline.","triggerScenarios":"Routing Anthropic-format requests through github_copilot (model like 'github_copilot/claude-sonnet-4' via the messages API) when the device-flow login was never completed, the cached access token expired and refresh fails, or the api-key cache write fails.","commonSituations":"Claude-Code-style clients pointed at litellm with github_copilot as backend before OAuth setup; token cache lost on container restart; Copilot seat revoked so refresh 401s; multiple litellm versions sharing one cache dir with incompatible schemas.","solutions":["Complete the OAuth device flow once on the host (litellm --login github_copilot) and persist the token cache directory.","Inspect str(e) to identify the sub-failure (refresh vs save vs missing token) and apply its fix.","Keep the token cache on a writable, persistent volume shared only by litellm processes of the same version.","Verify the GitHub account retains a Copilot subscription when refresh persistently 401s."],"exampleFix":"# before: messages API on unauthenticated host\nlitellm.anthropic_messages(model=\"github_copilot/claude-sonnet-4\", messages=[...])\n# -> AuthenticationError: Failed to refresh API key ...\n\n# after: ensure creds exist before serving traffic (startup check)\nfrom litellm.llms.github_copilot.authenticator import GitHubCopilotAuthenticator\ntry:\n    GitHubCopilotAuthenticator().get_api_key()\nexcept Exception:\n    raise SystemExit(\"github_copilot not authenticated; run 'litellm --login github_copilot'\")\nlitellm.anthropic_messages(model=\"github_copilot/claude-sonnet-4\", messages=[...])","handlingStrategy":"try-catch","validationCode":"from litellm.llms.github_copilot.authenticator import GitHubCopilotAuthenticator\n\nauth = GitHubCopilotAuthenticator()\ntry:\n    auth.get_api_key()\nexcept Exception as e:\n    raise SystemExit(f\"Copilot messages API not ready: {e}\")","typeGuard":null,"tryCatchPattern":"from litellm.exceptions import AuthenticationError\n\ntry:\n    resp = litellm.anthropic_messages(model=\"github_copilot/claude-sonnet-4\", messages=msgs)\nexcept AuthenticationError as e:\n    # str(e) is the wrapped GetAPIKeyError — fix the named root cause\n    if \"save API key\" in str(e):\n        fix_token_dir_permissions()\n    else:\n        raise SystemExit(f\"Operator action required: {e}\") from e","preventionTips":["Authenticate once per host and persist the token cache before enabling the messages proxy.","Never pass caller-controlled api_base on this path — it is ignored by design to protect the bearer token.","Add get_api_key() to the readiness probe of any service fronting the messages path.","Classify embedded causes before retrying; refresh failures need re-login."],"tags":["github-copilot","messages-api","authentication","oauth","wrapper"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}