{"record":{"id":"5c2d7aeeb263abf6","repo":"xtekky/gpt4free","slug":"device-code-expired-please-try-again","errorCode":null,"errorMessage":"Device code expired. Please try again.","messagePattern":"Device code expired\\. Please try again\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"g4f/Provider/github/githubOAuth2.py","lineNumber":150,"sourceCode":"        async with aiohttp.ClientSession() as session:\n            async with session.post(\n                GITHUB_TOKEN_ENDPOINT,\n                headers={\n                    \"Content-Type\": \"application/x-www-form-urlencoded\",\n                    \"Accept\": \"application/json\",\n                },\n                data=object_to_urlencoded(body_data),\n            ) as resp:\n                resp_json = await resp.json()\n\n                # Check for OAuth RFC 8628 responses\n                if \"error\" in resp_json:\n                    if resp_json[\"error\"] == \"authorization_pending\":\n                        return {\"status\": \"pending\"}\n                    if resp_json[\"error\"] == \"slow_down\":\n                        return {\"status\": \"pending\", \"slowDown\": True}\n                    if resp_json[\"error\"] == \"expired_token\":\n                        raise Exception(\"Device code expired. Please try again.\")\n                    if resp_json[\"error\"] == \"access_denied\":\n                        raise Exception(\"Authorization was denied by the user.\")\n                    raise Exception(\n                        f\"Token poll failed: {resp_json.get('error')} - {resp_json.get('error_description')}\"\n                    )\n\n                return resp_json\n\n    def isTokenValid(self, credentials: GithubCredentials) -> bool:\n        \"\"\"GitHub tokens don't expire by default, but we track expiry_date if set\"\"\"\n        if not credentials.get(\"access_token\"):\n            return False\n        expiry_date = credentials.get(\"expiry_date\")\n        if expiry_date is None:\n            # GitHub tokens don't expire unless explicitly set\n            return True\n        return time.time() * 1000 < expiry_date - TOKEN_REFRESH_BUFFER_MS\n","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/xtekky/gpt4free/blob/973504e1770928ed5fb82f43da528f441ad9ddc3/g4f/Provider/github/githubOAuth2.py#L132-L168","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nwhile True:\n    result = await client.pollDeviceToken({\"device_code\": saved_code})\n# after\nstart = time.monotonic()\nwhile time.monotonic() - start < expires_in - 10:\n    result = await client.pollDeviceToken({\"device_code\": saved_code})\n    if result.get(\"status\") != \"pending\":\n        break\n    await asyncio.sleep(interval)","handlingStrategy":"validation","validationCode":"import time\n\nif time.time() > flow_started_at + device_auth.get('expires_in', 900) - 30:\n    device_auth = await client.authorizeDevice()  # expired: get a new code\n    flow_started_at = time.time()","typeGuard":null,"tryCatchPattern":"try:\n    result = await client.pollDeviceToken({'device_code': code})\nexcept Exception as e:\n    if 'expired' in str(e).lower():\n        device_auth = await client.authorizeDevice()  # restart flow\n    else:\n        raise","preventionTips":["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"],"tags":["oauth","device-flow","expiry","github"],"backgroundTag":null,"analyzedSha":"973504e1770928ed5fb82f43da528f441ad9ddc3","analyzedAt":"2026-08-14T23:45:32.408Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}