decolua/9router · error · Error
Device code expired
Error message
Device code expired
What it means
While polling GitHub's token endpoint in the device flow, GitHub can answer `error: expired_token`, meaning the device code exceeded its lifetime (GitHub's device codes expire after ~15 minutes) because the user never completed browser authorization in time. pollAccessToken() prints 'Device code expired. Please try again.' and throws "Device code expired" at github.js:91.
Source
Thrown at src/lib/oauth/services/github.js:91
const data = await response.json();
if (data.access_token) {
spinner.succeed("GitHub authentication successful!");
return {
access_token: data.access_token,
token_type: data.token_type,
scope: data.scope,
};
} else if (data.error === "authorization_pending") {
// Continue polling
continue;
} else if (data.error === "slow_down") {
// Increase polling interval
interval += 5000;
continue;
} else if (data.error === "expired_token") {
spinner.fail("Device code expired. Please try again.");
throw new Error("Device code expired");
} else if (data.error === "access_denied") {
spinner.fail("Access denied by user.");
throw new Error("Access denied");
} else {
spinner.fail("Failed to get access token.");
throw new Error(data.error_description || data.error);
}
}
}
/**
* Get Copilot token using GitHub access token
*/
async getCopilotToken(accessToken) {
const response = await fetch(`${GITHUB_CONFIG.copilotTokenUrl}`, {
headers: {
Authorization: `Bearer ${accessToken}`, // GitHub API typically uses Bearer
Accept: "application/json",View on GitHub (pinned to 90b52e06ff)
Solutions
- Re-run the authentication flow and enter the new user code at https://github.com/login/device promptly.
- Complete the browser authorization immediately after the code is displayed instead of leaving it pending.
- If running headless, manually open the printed verification_uri on another device and enter the user code.
- Avoid long pauses between starting the flow and authorizing; restart if the flow sat for more than ~15 minutes.
Defensive patterns
Strategy: retry
Type guard
function isTokenExpiredError(data) {
return data !== null && typeof data === 'object' && data.error === 'expired_token';
} Try / catch
try {
const tokens = await service.pollAccessToken(deviceCode, verificationUri, userCode);
} catch (err) {
if (err.message === 'Device code expired') {
console.error('The device code timed out — restarting the flow with a fresh code...');
return authenticate(); // full restart issues a new device code
}
throw err;
} Prevention
- Enter the user code at the verification URL promptly after it is displayed.
- On headless machines, print the verification_uri and user_code prominently and open them on another device immediately.
- Treat 'Device code expired' as a normal restart signal — rerun the whole flow, never reuse the old device_code.
- Keep polling intervals within GitHub's guidance so slow_down adjustments don't stretch past the TTL.
When it happens
Trigger: pollAccessToken() received `{"error":"expired_token"}` from https://github.com/login/oauth/access_token because no successful authorization happened before the device code TTL elapsed.
Common situations: User saw the code but got distracted or never opened the browser; the machine running the CLI is headless so the verification URL was never visited; slow_down retries stretched polling past the expiry window.
Related errors
- `Device code request failed: ${error}`
- Access denied
- ${data.error_description || data.error}
- GitHub authentication failed: ${error.message}
- `CodeBuddy state request failed: ${await response.text()}`
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/d1820491e48782c3.
Report an issue: GitHub.