chatboxai/chatbox · error · Error

Qwen device authorization failed: ${text}

Error message

Qwen device authorization failed: ${text}

What it means

Thrown by Qwen's startDeviceFlow() when the initial POST to chat.qwen.ai/api/v1/oauth2/device/code returns non-2xx. The raw response body is interpolated. This is the first call that registers the device flow and yields the user_code and verification_uri; failure here prevents polling from ever starting.

Source

Thrown at src/main/oauth/providers/qwen.ts:73

    const response = await fetch(`${QWEN_BASE_URL}/api/v1/oauth2/device/code`, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/x-www-form-urlencoded',
        'x-request-id': randomUUID(),
      },
      body: new URLSearchParams({
        client_id: QWEN_CLIENT_ID,
        scope: 'openid profile email model.completion',
        code_challenge: challenge,
        code_challenge_method: 'S256',
      }).toString(),
    })

    if (!response.ok) {
      const text = await response.text()
      throw new Error(`Qwen device authorization failed: ${text}`)
    }

    const payload = (await response.json()) as {
      device_code: string
      user_code: string
      verification_uri: string
      verification_uri_complete?: string
      expires_in?: number
      interval?: number
    }

    pendingDeviceCode = payload.device_code
    pendingVerifier = verifier
    pendingIntervalMs = payload.interval ? payload.interval * 1000 : 2000

    return {
      userCode: payload.user_code,
      verificationUri: payload.verification_uri_complete || payload.verification_uri,

View on GitHub (pinned to 81571269ad)

Solutions

  1. Read the interpolated body for Qwen's reason — 4xx typically means client/scope/PKCE mismatch, 5xx means retry.
  2. Verify QWEN_CLIENT_ID and QWEN_BASE_URL against the current Qwen portal contract.
  3. Confirm code_challenge is base64url SHA-256 of the verifier and code_challenge_method is 'S256'.
  4. For 5xx or network errors, retry once with backoff before surfacing.

Example fix

// before
if (!response.ok) {
  const text = await response.text()
  throw new Error(`Qwen device authorization failed: ${text}`)
}

// after
if (!response.ok) {
  const text = await response.text()
  log.error('[OAuth:Qwen] device code request failed', response.status, text)
  if (response.status >= 500) throw new TransientError('Qwen unavailable, retry')
  throw new Error(`Qwen device authorization failed (${response.status}): ${text}`)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Sanity-check PKCE before the request to Qwen.
function assertQwenPkce(verifier: string, challenge: string) {
  if (verifier.length < 32) throw new Error('verifier too short')
  if (!/^[A-Za-z0-9_-]+$/.test(challenge)) throw new Error('challenge must be base64url')
}

Try / catch

try {
  return await provider.startDeviceFlow()
} catch (e) {
  const msg = String(e)
  if (/Qwen device authorization failed/i.test(msg)) {
  if (/5\d\d|network|fetch/i.test(msg)) return await provider.startDeviceFlow() // transient retry
  throw new Error('Qwen rejected the device request — check client_id, scope, base URL, and PKCE.')
  }
  throw e
}

Prevention

When it happens

Trigger: QWEN_CLIENT_ID (f0304373b74a44d2b584a3fb70ca9e56) rejected by Qwen; requested scope (openid profile email model.completion) not permitted for the client; PKCE code_challenge malformed or not S256; QWEN_BASE_URL changed; x-request-id header rejected; Qwen API outage or rate limit.

Common situations: Qwen rotated the public client_id; the base URL shifted (e.g. regional endpoint); a corporate proxy rewrote the form body or stripped the x-request-id header; the scope set was changed server-side.

Related errors


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