{"record":{"id":"085719b6d3971926","repo":"chatboxai/chatbox","slug":"qwen-oauth-failed-payload-error-description","errorCode":null,"errorMessage":"Qwen OAuth failed: ${payload.error_description || payload.error || response.statusText}","messagePattern":"Qwen OAuth failed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/oauth/providers/qwen.ts","lineNumber":152,"sourceCode":"              refreshToken: payload.refresh_token,\n              expiresAt: toExpiresAt(payload.expires_in),\n            }\n          }\n        } else {\n          const payload = (await response.json().catch(() => ({}))) as {\n            error?: string\n            error_description?: string\n          }\n\n          if (payload.error === 'authorization_pending') {\n            continue\n          }\n          if (payload.error === 'slow_down') {\n            intervalMs = Math.min(intervalMs + 2000, 10_000)\n            continue\n          }\n\n          throw new Error(`Qwen OAuth failed: ${payload.error_description || payload.error || response.statusText}`)\n        }\n      }\n\n      throw new Error('Qwen OAuth timed out waiting for authorization.')\n    } finally {\n      pendingDeviceCode = null\n      pendingVerifier = null\n      pendingIntervalMs = 2000\n    }\n  },\n\n  async refreshToken(credentials) {\n    if (!credentials.refreshToken) {\n      log.warn('[OAuth:Qwen] No refresh token available, returning existing credentials')\n      return credentials\n    }\n\n    const response = await fetch(`${QWEN_BASE_URL}/api/v1/oauth2/token`, {","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/oauth/providers/qwen.ts#L134-L170","documentation":"Thrown during the Qwen OAuth2 device-flow token poll when the token endpoint returns a non-OK response whose error is neither 'authorization_pending' nor 'slow_down'. These two are the expected transient states; any other error code (e.g. 'expired_token', 'access_denied', 'invalid_grant', 'incorrect_device_code') is treated as terminal. The message surfaces whatever the server supplied: error_description preferred, then error, then response.statusText.","triggerScenarios":"POST to ${QWEN_BASE_URL}/api/v1/oauth2/token with grant_type=urn:ietf:params:oauth:grant-type:device_code where the user denied consent (access_denied), the device_code expired (expired_token, 10 min deadline vs server's lifetime), the wrong client_id/device_code/code_verifier was sent (invalid_grant, incorrect_client, incorrect_device_code), or the server returns a malformed body that fails JSON.parse (payload becomes {} and falls through to statusText).","commonSituations":"User closes the browser without completing consent; user waits longer than the device_code TTL; the app was restarted mid-flow so pendingDeviceCode/pendingVerifier no longer match a live server-side code; QWEN_CLIENT_ID changed; Qwen API outage returning HTML (statusText 'Service Unavailable').","solutions":["Re-initiate the device flow with authenticate() so a fresh device_code + code_verifier are issued and the user gets a new verification URL.","Check the embedded error code: 'expired_token'/'access_denied' => restart flow and have the user complete consent within ~10 min; 'incorrect_*' => verify QWEN_CLIENT_ID and that pendingDeviceCode/pendingVerifier were not reset by a prior finally block.","Inspect response.statusText in the message — if it is non-JSON (e.g. HTML error page), treat it as a Qwen-side outage and retry with backoff rather than re-prompting.","If reproducible, capture the raw response body once to confirm the exact OAuth error code before adjusting the flow."],"exampleFix":"// before: terminal throw on any non-transient error\nthrow new Error(`Qwen OAuth failed: ${payload.error_description || payload.error || response.statusText}`)\n\n// after: classify so 'expired_token' auto-restarts the flow\nif (payload.error === 'expired_token') { throw new QwenDeviceCodeExpiredError() }\nthrow new Error(`Qwen OAuth failed: ${payload.error_description || payload.error || response.statusText}`)","handlingStrategy":"try-catch","validationCode":"if (!pendingDeviceCode || !pendingVerifier) { throw new Error('Device flow not initialized; call startDeviceFlow first') }","typeGuard":"function isQwenOAuthError(e: unknown): e is Error {\n  return e instanceof Error && e.message.startsWith('Qwen OAuth failed:')\n}","tryCatchPattern":"try {\n  return await qwenProvider.authenticate(signal)\n} catch (e) {\n  if (isQwenOAuthError(e)) { // surface server error_description to user, offer re-auth\n  }\n  throw e\n}","preventionTips":["Complete device-flow consent promptly within the 10-minute deadline.","Do not reset pendingDeviceCode/pendingVerifier between polls (the finally block clears them on exit).","Keep QWEN_CLIENT_ID constant across releases."],"tags":["oauth","qwen","device-flow","network","typescript"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}