xtekky/gpt4free · error · Exception

Device code expired. Please try again.

Error message

Device code expired. Please try again.

What it means

A bare Exception raised inside pollDeviceToken when the token polling endpoint returns the RFC 8628 error 'expired_token'. Device codes are short-lived (typically ~15 minutes, per expires_in from the initial authorization); once expired, polling can never succeed and the whole device flow must be restarted with a new authorization request.

Source

Thrown at g4f/Provider/github/githubOAuth2.py:150

        async with aiohttp.ClientSession() as session:
            async with session.post(
                GITHUB_TOKEN_ENDPOINT,
                headers={
                    "Content-Type": "application/x-www-form-urlencoded",
                    "Accept": "application/json",
                },
                data=object_to_urlencoded(body_data),
            ) as resp:
                resp_json = await resp.json()

                # Check for OAuth RFC 8628 responses
                if "error" in resp_json:
                    if resp_json["error"] == "authorization_pending":
                        return {"status": "pending"}
                    if resp_json["error"] == "slow_down":
                        return {"status": "pending", "slowDown": True}
                    if resp_json["error"] == "expired_token":
                        raise Exception("Device code expired. Please try again.")
                    if resp_json["error"] == "access_denied":
                        raise Exception("Authorization was denied by the user.")
                    raise Exception(
                        f"Token poll failed: {resp_json.get('error')} - {resp_json.get('error_description')}"
                    )

                return resp_json

    def isTokenValid(self, credentials: GithubCredentials) -> bool:
        """GitHub tokens don't expire by default, but we track expiry_date if set"""
        if not credentials.get("access_token"):
            return False
        expiry_date = credentials.get("expiry_date")
        if expiry_date is None:
            # GitHub tokens don't expire unless explicitly set
            return True
        return time.time() * 1000 < expiry_date - TOKEN_REFRESH_BUFFER_MS

View on GitHub (pinned to 973504e177)

Solutions

  1. Restart the flow: call oauth_begin again to get a fresh device_code and user_code
  2. Poll at the advertised interval and stop well before expires_in seconds elapse
  3. Discard persisted device_codes on process restart instead of reusing them

Example fix

# before
while True:
    result = await client.pollDeviceToken({"device_code": saved_code})
# after
start = time.monotonic()
while time.monotonic() - start < expires_in - 10:
    result = await client.pollDeviceToken({"device_code": saved_code})
    if result.get("status") != "pending":
        break
    await asyncio.sleep(interval)
Defensive patterns

Strategy: validation

Validate before calling

import time

if time.time() > flow_started_at + device_auth.get('expires_in', 900) - 30:
    device_auth = await client.authorizeDevice()  # expired: get a new code
    flow_started_at = time.time()

Try / catch

try:
    result = await client.pollDeviceToken({'device_code': code})
except Exception as e:
    if 'expired' in str(e).lower():
        device_auth = await client.authorizeDevice()  # restart flow
    else:
        raise

Prevention

When it happens

Trigger: The user did not enter the user_code at the verification URI before expires_in elapsed, and oauth_poll was called after expiry; or polling resumed from a persisted device_code from a previous session.

Common situations: User stepped away from the terminal during login; polling loop paused/blocked (debugger, suspended process) past the expiry window; retrying an old flow after failure.

Related errors


AI-assisted analysis of xtekky/gpt4free@973504e177 (2026-08-14). Data as JSON: /api/errors/5c2d7aeeb263abf6. Report an issue: GitHub.