{"record":{"id":"ed92c742c5f3f5ce","repo":"BerriAI/litellm","slug":"failed-to-get-access-token-after-3-attempts","errorCode":null,"errorMessage":"Failed to get access token after 3 attempts","messagePattern":"Failed to get access token after 3 attempts","errorType":"exception","errorClass":"GetAccessTokenError","httpStatus":401,"severity":"critical","filePath":"litellm/llms/github_copilot/authenticator.py","lineNumber":74,"sourceCode":"                    return access_token\n        except OSError:\n            verbose_logger.warning(\"No existing access token found or error reading file\")\n\n        for attempt in range(3):\n            verbose_logger.debug(\"Access token acquisition attempt %s/3\", attempt + 1)\n            try:\n                access_token = self._login()\n                try:\n                    with open(self.access_token_file, \"w\") as f:\n                        f.write(access_token)\n                except OSError:\n                    verbose_logger.error(\"Error saving access token to file\")\n                return access_token\n            except (GetDeviceCodeError, GetAccessTokenError, RefreshAPIKeyError) as e:\n                verbose_logger.warning(\"Failed attempt %s: %s\", attempt + 1, e)\n                continue\n\n        raise GetAccessTokenError(\n            message=\"Failed to get access token after 3 attempts\",\n            status_code=401,\n        )\n\n    def get_api_key(self) -> str:\n        \"\"\"\n        Get the API key, refreshing if necessary.\n\n        Returns:\n            str: The GitHub Copilot API key.\n\n        Raises:\n            GetAPIKeyError: If unable to obtain an API key.\n        \"\"\"\n        try:\n            with open(self.api_key_file, \"r\") as f:\n                api_key_info = json.load(f)\n                if api_key_info.get(\"expires_at\", 0) > datetime.now().timestamp():","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/github_copilot/authenticator.py#L56-L92","documentation":"Raised by the GitHub Copilot authenticator after the device-flow login loop exhausts 3 attempts without obtaining an access token. Each attempt can fail with GetDeviceCodeError (could not start device flow), GetAccessTokenError (exchange failed, typically expired/bad user code), or RefreshAPIKeyError; the per-attempt reasons are logged as warnings, and the final GetAccessTokenError carries status 401. This is a client-side OAuth failure, not an HTTP error from a completion call.","triggerScenarios":"Calling get_access_token() (directly or via the first litellm github_copilot request) when: the user never completed the device-flow browser prompt within the code's validity window (polling times out each attempt), GitHub returns 400 'bad_verification_code' because the code expired between polls, or the device-code request itself fails (network/rate-limit to github.com/login/device/code).","commonSituations":"Headless servers where nobody opens the verification URL (https://github.com/login/device) in time; clock skew or slow polling causing code expiry; running multiple processes that each try device flow concurrently against GitHub rate limits; expired cached creds.txt forcing re-login attempts inside an unattended job.","solutions":["Check the verbose_logger warnings — they show which stage failed each attempt (device code vs token exchange vs refresh).","Run the OAuth flow interactively once (litellm's copilot login flow or a manual device-flow script), complete the browser prompt at github.com/login/device within ~15 minutes, and let the token cache (access_token.txt) be written so headless runs reuse it.","Ensure the token cache directory is writable and persistent across deployments so device flow is a one-time setup.","If behind a proxy/firewall, verify egress to github.com and githubcopilot.com is open."],"exampleFix":"# before: headless call triggers device flow nobody completes -> 3 failed attempts\nimport litellm\nlitellm.completion(model=\"github_copilot/gpt-4o\", messages=[...])\n\n# after: authenticate once interactively, cache the token, then run headless\n# terminal 1 (one-time):  litellm --login github_copilot   # completes browser device flow\nimport litellm\nlitellm.completion(model=\"github_copilot/gpt-4o\", messages=[...])  # reuses cached token","handlingStrategy":"fallback","validationCode":"import os, pathlib\n\ntoken_file = pathlib.Path(\"~/.litellm/github_copilot/access_token.txt\").expanduser()\nif not token_file.exists() or not token_file.read_text(encoding=\"utf-8\").strip():\n    raise SystemExit(\n        \"github_copilot is not authenticated. Run the device-flow login interactively \"\n        \"(litellm --login github_copilot) and persist the token dir before headless use.\"\n    )","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 \"after 3 attempts\" in str(e):\n        # device flow never completed — cannot self-heal headlessly\n        raise SystemExit(\"Run 'litellm --login github_copilot' interactively, then restart\") from e\n    raise","preventionTips":["Complete the OAuth device flow interactively once per host and persist the token dir on a writable volume.","Never rely on auto-login in headless jobs — device flow requires a human at github.com/login/device.","Monitor verbose warnings ('Failed attempt N') as an early signal of login trouble.","Ensure egress to github.com and githubcopilot.com is open before starting the flow."],"tags":["github-copilot","oauth","device-flow","authentication","retry-exhausted"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}