{"record":{"id":"2a42b0c2f6f342c0","repo":"decolua/9router","slug":"message","errorCode":null,"errorMessage":"${message}","messagePattern":"\\$\\{message\\}","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/qoder.js","lineNumber":125,"sourceCode":"        \"User-Agent\": \"Go-http-client/2.0\",\n      },\n    });\n\n    // Pending — server has registered the device code but the user hasn't\n    // finished the browser flow yet. Both 202 and 404 mean \"keep polling\".\n    if (response.status === 202 || response.status === 404) {\n      return { status: \"pending\" };\n    }\n\n    const text = await response.text();\n\n    if (!response.ok) {\n      let message = `Qoder device token poll failed: HTTP ${response.status}`;\n      try {\n        const body = JSON.parse(text);\n        if (body.message) message = `Qoder device token poll failed: ${body.message}`;\n      } catch {}\n      throw new Error(message);\n    }\n\n    let body;\n    try {\n      body = JSON.parse(text);\n    } catch (err) {\n      throw new Error(`Qoder device token poll: invalid JSON response (${err.message})`);\n    }\n\n    // Defensive: 200 + empty token means the upstream changed shape.\n    if (!body.token) {\n      throw new Error(\"Qoder device token poll returned 200 but no token\");\n    }\n\n    const expireMs = QoderService.parseExpiry(body.expires_at, body.expires_in);\n\n    return {\n      status: \"ok\",","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/qoder.js#L107-L143","documentation":"Thrown when Qoder's device-token poll endpoint answers with a non-2xx status. The message is 'Qoder device token poll failed: HTTP <status>', upgraded to include the upstream JSON 'message' field when the body parses as JSON. Per the service contract, upstream returns 202/404 while the user is still authorizing; any other non-ok status reaching this throw is a terminal failure of the device flow.","triggerScenarios":"GET to QODER_DEVICE_TOKEN_URL returns 400/401/403/500 etc. — e.g. an expired or invalid nonce, wrong verifier (PKCE mismatch), or Qoder outage. Note 202/404 while waiting are expected polling states, not this error, if handled by the caller's retry loop.","commonSituations":"Polling after the device code expired; reusing a nonce from a previous flow; Qoder API version change altering the endpoint contract; corporate proxy returning 502 with an HTML body (which keeps the generic HTTP status message).","solutions":["Read the included upstream message in the error text to identify the exact cause (e.g. expired nonce vs unauthorized).","Treat 202/404 as 'keep waiting' in your polling loop and only surface other statuses as fatal.","Restart the device flow (new nonce + code verifier) if the poll indicates expiry or invalid state.","Check Qoder service status / network connectivity if the status is 5xx."],"exampleFix":"// before\nconst t = await qoder.pollDeviceToken({ nonce, codeVerifier }); // throws on 404 while waiting\n// after\ntry {\n  const t = await qoder.pollDeviceToken({ nonce, codeVerifier });\n} catch (e) {\n  if (/HTTP (202|404)/.test(e.message)) return continuePolling();\n  throw e;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const result = await qoder.pollDeviceToken({ nonce, codeVerifier });\n} catch (e) {\n  if (/HTTP 202|HTTP 404/.test(e.message)) { await sleep(pollInterval); continue; } // still waiting\n  if (/HTTP 5\\d\\d/.test(e.message)) { await sleep(backoff); continue; }             // transient upstream\n  throw e; // 400/401/403: state is bad, restart device flow\n}","preventionTips":["Implement a bounded polling loop that treats 202/404 as pending, not fatal.","Restart the device flow when the poll indicates expiry instead of retrying a dead nonce.","Surface the upstream message (already embedded) to logs for diagnosis."],"tags":["oauth","device-flow","http","network"],"backgroundTag":"oauth-token-poll-http-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}