BerriAI/litellm · error · GetAccessTokenError

Polling failed

Error message

Polling failed: {exc}

What it means

GetAccessTokenError raised while polling the ChatGPT device-authorization endpoint: the poll returned an HTTPStatusError whose code is not 403/404 (those two are retried as 'keep waiting'). The message contains the underlying httpx error and the authenticator attaches the upstream status code, so you can tell a 400 (expired/invalid device code) from a 5xx (upstream incident).

Solutions

  1. Restart the login flow to get a fresh device code and approve it promptly at the printed verify URL
  2. If the status is 429, slow down — do not run multiple logins concurrently from the same host
  3. Upgrade litellm if the poll endpoint contract changed
  4. Check the status code embedded in the message to distinguish stale-code (400) from upstream failure (5xx)
Defensive patterns

Strategy: retry

Try / catch

from litellm.llms.chatgpt.authenticator import GetAccessTokenError

try:
    tokens = authenticator.login()
except GetAccessTokenError as e:
    if "Polling failed" in str(e) and e.status_code in (400, 429):
        # stale device code or rate limit: restart with a fresh code
        tokens = authenticator.login()  # prompts a new device code
    else:
        raise

Prevention

When it happens

Trigger: Polling CHATGPT_DEVICE_CODE_URL during device login and receiving e.g. 400 (device_auth_id expired or invalid), 401, or 500 — any non-403/404 error status aborts with this error.

Common situations: User took too long to approve and the device code went stale mid-poll; rate-limited polling; auth.openai.com incidents; reused device_auth_id from a previous login attempt.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/4debe053960bba9e. Report an issue: GitHub.

Appendix: source

Thrown at litellm/llms/chatgpt/authenticator.py:232

                    if all(
                        key in data
                        for key in (
                            "authorization_code",
                            "code_challenge",
                            "code_verifier",
                        )
                    ):
                        return data
                if resp.status_code in (403, 404):
                    time.sleep(max(interval, DEVICE_CODE_POLL_SLEEP_SECONDS))
                    continue
                resp.raise_for_status()
            except httpx.HTTPStatusError as exc:
                status_code = exc.response.status_code if exc.response else None
                if status_code in (403, 404):
                    time.sleep(max(interval, DEVICE_CODE_POLL_SLEEP_SECONDS))
                    continue
                raise GetAccessTokenError(
                    message=f"Polling failed: {exc}",
                    status_code=exc.response.status_code,
                )
            except Exception as exc:
                raise GetAccessTokenError(
                    message=f"Polling failed: {exc}",
                    status_code=400,
                )
            time.sleep(max(interval, DEVICE_CODE_POLL_SLEEP_SECONDS))

        raise GetAccessTokenError(
            message="Timed out waiting for device authorization",
            status_code=408,
        )

    def _exchange_code_for_tokens(self, code_data: dict[str, str]) -> dict[str, str]:
        try:
            client: Final = _get_httpx_client()

View on GitHub (pinned to 77b7c6c40c)