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
- 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.
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
- 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.
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
- Failed to refresh API key: {e}
- Failed to refresh API key after maximum retries
- str(e)
- GitHub Copilot API key is required. Please authenticate via
- str(e)
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/ed92c742c5f3f5ce.
Report an issue: GitHub.