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

  1. Re-run the authentication flow and enter the new user code at https://github.com/login/device promptly.
  2. Complete the browser authorization immediately after the code is displayed instead of leaving it pending.
  3. If running headless, manually open the printed verification_uri on another device and enter the user code.
  4. 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

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


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/d1820491e48782c3. Report an issue: GitHub.