{"record":{"id":"0e2c9cce71770618","repo":"BerriAI/litellm","slug":"failed-to-refresh-api-key-e","errorCode":null,"errorMessage":"Failed to refresh API key: {e}","messagePattern":"Failed to refresh API key: (.+?)","errorType":"exception","errorClass":"GetAPIKeyError","httpStatus":401,"severity":"critical","filePath":"litellm/llms/github_copilot/authenticator.py","lineNumber":126,"sourceCode":"            api_key_info = self._refresh_api_key()\n            with open(self.api_key_file, \"w\") as f:\n                json.dump(api_key_info, f)\n            token: Final = api_key_info.get(\"token\")\n            if token:\n                return token\n            else:\n                raise GetAPIKeyError(\n                    message=\"API key response missing token\",\n                    status_code=401,\n                )\n        except OSError as e:\n            verbose_logger.error(\"Error saving API key to file: %s\", e)\n            raise GetAPIKeyError(\n                message=f\"Failed to save API key: {e}\",\n                status_code=500,\n            )\n        except RefreshAPIKeyError as e:\n            raise GetAPIKeyError(\n                message=f\"Failed to refresh API key: {e}\",\n                status_code=401,\n            )\n\n    def get_api_base(self) -> str | None:\n        \"\"\"\n        Get the API endpoint from the api-key.json file.\n\n        Returns:\n            Optional[str]: The GitHub Copilot API endpoint, or None if not found.\n        \"\"\"\n        try:\n            with open(self.api_key_file, \"r\") as f:\n                api_key_info: Final = json.load(f)\n                endpoints: Final = api_key_info.get(\"endpoints\", {})\n                api_endpoint: Final = endpoints.get(\"api\")\n                return api_endpoint\n        except (OSError, json.JSONDecodeError, KeyError) as e:","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/github_copilot/authenticator.py#L108-L144","documentation":"Raised in get_api_key() when the underlying token refresh (_refresh_api_key) exhausts its own retry loop and throws RefreshAPIKeyError, which is then wrapped as GetAPIKeyError(401). The inner message (from the original failure — HTTP errors or unexpected exceptions during refresh) is chained into this message. Practically this means the cached Copilot access token can no longer be exchanged for a Copilot session API key.","triggerScenarios":"The Copilot token endpoint rejects the stored access token on every retry attempt: access token expired past its refresh window (cached creds too old), GitHub revoked the OAuth grant, or the refresh HTTP call fails 3 times (5xx/rate-limit/network). Each underlying error is logged via verbose_logger.error before the wrapper raises.","commonSituations":"Long-lived deployments whose cached access token expired and cannot refresh (GitHub access tokens for Copilot internal flow are short-lived); users revoking the GitHub Copilot app authorization; sustained GitHub API rate limiting from too many refresh calls; litellm version drift where the refresh endpoint/headers changed (editor version headers matter).","solutions":["Re-run the device-flow login to obtain a fresh access token (delete cached token files, then authenticate interactively once).","Confirm your GitHub account still has an active Copilot subscription/seat and the OAuth grant was not revoked (GitHub Settings -> Applications).","Check verbose logs for the per-attempt refresh errors — 401 means dead access token, 403/429 means rate limits or entitlement loss.","Upgrade litellm: the Copilot authenticator's endpoints and required headers (editor-version etc.) have changed across releases."],"exampleFix":"# before: expired cached access token -> RefreshAPIKeyError -> GetAPIKeyError(401) on every call\nimport litellm\nlitellm.completion(model=\"github_copilot/claude-sonnet-4\", messages=[...])\n\n# after: clear cache and re-auth via device flow, then resume\nimport shutil, pathlib\ncache = pathlib.Path(\"~/.litellm/github_copilot\").expanduser()\nif cache.exists(): shutil.rmtree(cache)\n# run `litellm --login github_copilot` once, verify at github.com/login/device\nlitellm.completion(model=\"github_copilot/claude-sonnet-4\", messages=[...])","handlingStrategy":"fallback","validationCode":"import json, pathlib, time\n\ninfo_path = pathlib.Path(\"~/.litellm/github_copilot/api-key.json\").expanduser()\nif info_path.exists():\n    info = json.loads(info_path.read_text())\n    expires = info.get(\"expires_at\", 0)\n    if expires and time.time() > expires - 300:\n        print(\"warning: Copilot api-key near/past expiry — expect refresh; ensure access token is still valid\")","typeGuard":null,"tryCatchPattern":"from litellm.exceptions import AuthenticationError\n\ntry:\n    resp = litellm.completion(model=\"github_copilot/gpt-4o\", messages=msgs)\nexcept AuthenticationError as e:\n    if \"Failed to refresh API key\" in str(e):\n        # access token dead past refresh window — only cure is re-login\n        raise SystemExit(\"Copilot session expired; re-run 'litellm --login github_copilot' to re-authenticate\") from e\n    raise","preventionTips":["Schedule a periodic interactive re-auth (or scripted device flow) before the access token's refresh window lapses.","Alert when refresh failures repeat — proactive re-login beats headless failure.","Keep exactly one litellm version owning the token cache dir.","Verify the GitHub account retains its Copilot entitlement after org changes."],"tags":["github-copilot","authentication","token-refresh","retry-exhausted","oauth"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}