BerriAI/litellm · critical · GetAccessTokenError

Failed to get access token after 3 attempts

Error message

Failed to get access token after 3 attempts

What it means

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.

Source

Thrown at litellm/llms/github_copilot/authenticator.py:74

                    return access_token
        except OSError:
            verbose_logger.warning("No existing access token found or error reading file")

        for attempt in range(3):
            verbose_logger.debug("Access token acquisition attempt %s/3", attempt + 1)
            try:
                access_token = self._login()
                try:
                    with open(self.access_token_file, "w") as f:
                        f.write(access_token)
                except OSError:
                    verbose_logger.error("Error saving access token to file")
                return access_token
            except (GetDeviceCodeError, GetAccessTokenError, RefreshAPIKeyError) as e:
                verbose_logger.warning("Failed attempt %s: %s", attempt + 1, e)
                continue

        raise GetAccessTokenError(
            message="Failed to get access token after 3 attempts",
            status_code=401,
        )

    def get_api_key(self) -> str:
        """
        Get the API key, refreshing if necessary.

        Returns:
            str: The GitHub Copilot API key.

        Raises:
            GetAPIKeyError: If unable to obtain an API key.
        """
        try:
            with open(self.api_key_file, "r") as f:
                api_key_info = json.load(f)
                if api_key_info.get("expires_at", 0) > datetime.now().timestamp():

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Check the verbose_logger warnings — they show which stage failed each attempt (device code vs token exchange vs refresh).
  2. 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.
  3. Ensure the token cache directory is writable and persistent across deployments so device flow is a one-time setup.
  4. If behind a proxy/firewall, verify egress to github.com and githubcopilot.com is open.

Example fix

# before: headless call triggers device flow nobody completes -> 3 failed attempts
import litellm
litellm.completion(model="github_copilot/gpt-4o", messages=[...])

# after: authenticate once interactively, cache the token, then run headless
# terminal 1 (one-time):  litellm --login github_copilot   # completes browser device flow
import litellm
litellm.completion(model="github_copilot/gpt-4o", messages=[...])  # reuses cached token
Defensive patterns

Strategy: fallback

Validate before calling

import os, pathlib

token_file = pathlib.Path("~/.litellm/github_copilot/access_token.txt").expanduser()
if not token_file.exists() or not token_file.read_text(encoding="utf-8").strip():
    raise SystemExit(
        "github_copilot is not authenticated. Run the device-flow login interactively "
        "(litellm --login github_copilot) and persist the token dir before headless use."
    )

Try / catch

from litellm.exceptions import AuthenticationError

try:
    resp = litellm.completion(model="github_copilot/gpt-4o", messages=msgs)
except AuthenticationError as e:
    if "after 3 attempts" in str(e):
        # device flow never completed — cannot self-heal headlessly
        raise SystemExit("Run 'litellm --login github_copilot' interactively, then restart") from e
    raise

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/ed92c742c5f3f5ce. Report an issue: GitHub.