chatboxai/chatbox · warning · Error

Device flow timed out

Error message

Device flow timed out

What it means

Thrown when the device-flow polling loop in waitForToken() reaches its 10-minute hard deadline (Date.now() + 10*60*1000) without ever receiving an access_token and without GitHub returning a terminal error. It is a guard against the user simply never completing authorization. The finally block clears pendingDeviceCode, so the flow must be restarted from scratch.

Source

Thrown at src/main/oauth/providers/github-copilot.ts:128

          // Use the GitHub access token directly as the Copilot API credential
          // (same approach as openllmprovider)
          return {
            accessToken: raw.access_token,
            // No refresh token — the GitHub access token doesn't expire
            // but the Copilot API session may need re-auth periodically
          }
        }

        if (raw.error === 'authorization_pending') continue
        if (raw.error === 'slow_down') {
          intervalMs += 5000
          continue
        }

        throw new Error(`Device flow failed: ${raw.error}`)
      }

      throw new Error('Device flow timed out')
    } finally {
      pendingDeviceCode = null
    }
  },

  async refreshToken(credentials) {
    // GitHub access tokens from device flow don't expire in the traditional sense.
    // Just return the existing credentials.
    return credentials
  },
}

View on GitHub (pinned to 81571269ad)

Solutions

  1. Catch this specific timeout and restart via startDeviceFlow() to issue a fresh device_code and user_code.
  2. Surface a clear UI message ('Login timed out — click to try again') rather than a generic error.
  3. Consider bumping the deadline if GitHub's documented device-code lifetime exceeds 10 minutes.

Example fix

// before
const creds = await provider.waitForToken(signal)

// after
try {
  const creds = await provider.waitForToken(signal)
  return creds
} catch (e) {
  if (/timed out/i.test(String(e))) {
    const restarted = await provider.startDeviceFlow()
    throw new ReauthRequiredError(restarted)
  }
  throw e
}
Defensive patterns

Strategy: retry

Type guard

function isDeviceFlowTimeout(e: unknown): boolean {
  return e instanceof Error && /Device flow timed out/i.test(e.message)
}

Try / catch

try {
  return await provider.waitForToken(signal)
} catch (e) {
  if (isDeviceFlowTimeout(e)) {
  // restart with a fresh device code + user code
  const fresh = await provider.startDeviceFlow()
  throw new ReauthRequiredError(fresh)
  }
  throw e
}

Prevention

When it happens

Trigger: User opened the verification URL but never entered the code or never approved; user is on a slow connection and the approval round-trip exceeded 10 minutes; the device_code lifetime is longer than this deadline and the user authorized just after timeout.

Common situations: Backgrounded app where the user forgot to finish; user got distracted mid-login; clock differences making the deadline trigger early.

Understand the failure class

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/77c05e7d34add5a8. Report an issue: GitHub.