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
- Restart the flow: call oauth_begin again to get a fresh device_code and user_code
- Poll at the advertised interval and stop well before expires_in seconds elapse
- 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
- Record expires_in from the authorization response and enforce a deadline in your poll loop
- Never reuse a device_code across process restarts
- Remind the user of the verification URL and user_code immediately after starting the flow
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
- Device authorization failed {resp.status}: {resp_json}
- Device authorization error: {resp_json.get('error')} - {resp
- Authorization was denied by the user.
- device_code is required for polling
- Device authorization failed {resp.status}: {resp_json}
AI-assisted analysis of xtekky/gpt4free@973504e177 (2026-08-14).
Data as JSON: /api/errors/5c2d7aeeb263abf6.
Report an issue: GitHub.