{"record":{"id":"15bcd1f2b3ffaa82","repo":"decolua/9router","slug":"trae-exchangetoken-failed-lasterr","errorCode":null,"errorMessage":"`Trae ExchangeToken failed: ${lastErr}`","messagePattern":"`Trae ExchangeToken failed: (.+?)`","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/oauth/providers/trae.js","lineNumber":151,"sourceCode":"      if (!res.ok) { lastErr = `${url} HTTP ${res.status}`; continue; }\n      let data; try { data = JSON.parse(text); } catch { lastErr = `${url} invalid JSON`; continue; }\n      const accessToken = extractJsonPath(data, [\n        [\"Result\", \"AccessToken\"], [\"Result\", \"accessToken\"], [\"result\", \"access_token\"], [\"accessToken\"],\n      ]);\n      if (!accessToken) {\n        const msg = extractJsonPath(data, [[\"message\"], [\"msg\"], [\"error\"], [\"Result\", \"Message\"]]) || \"missing AccessToken\";\n        lastErr = `${url} ${msg}`;\n        continue;\n      }\n      return {\n        accessToken,\n        refreshToken: extractJsonPath(data, [[\"Result\", \"RefreshToken\"], [\"result\", \"refresh_token\"], [\"refreshToken\"]]) || refreshToken,\n        expiresIn: null, // ExchangeToken returns ExpiresAt (absolute), converted below\n        expiresAt: extractJsonPath(data, [[\"Result\", \"ExpiresAt\"], [\"Result\", \"expiresAt\"], [\"result\", \"expires_at\"], [\"expiresAt\"]]),\n      };\n    } catch (e) { lastErr = `${url} ${e.message}`; }\n  }\n  throw new Error(`Trae ExchangeToken failed: ${lastErr}`);\n}\n\n// POST GetUserInfo with x-cloudide-token → identity fields used by SOLO common_params.\nasync function fetchTraeUserInfo(accessToken) {\n  for (const origin of traeApiOrigins()) {\n    const url = `${origin.replace(/\\/$/, \"\")}${TRAE_CONFIG.getUserInfoPath}`;\n    try {\n      const res = await fetch(url, {\n        method: \"POST\",\n        headers: {\n          Accept: \"application/json\",\n          \"Content-Type\": \"application/json\",\n          \"User-Agent\": TRAE_CONFIG.userAgent,\n          \"x-cloudide-token\": accessToken,\n        },\n        body: JSON.stringify({}),\n      });\n      if (!res.ok) continue;","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/providers/trae.js#L133-L169","documentation":"Thrown by fetchTraeExchangeToken after trying every allowed Trae API origin with the ExchangeToken POST: each attempt threw or failed, and lastErr holds the last error. The refresh token obtained from the callback could not be exchanged for access credentials, so login cannot complete.","triggerScenarios":"All allowlisted API origins fail: network/DNS/proxy blocks the Trae API hosts, ExchangeToken endpoint returns errors (invalid/expired refreshToken, 4xx/5xx), TLS failures, or the response lacks the expected token fields causing an extract/parse error.","commonSituations":"Expired refreshToken from a slow callback round-trip; corporate proxy blocking Trae API origins; Trae API incident; response schema change breaks extractJsonPath so the code records a parse error.","solutions":["Read lastErr in the message: a fetch exception means network/unreachable; an HTTP/parse message points at the API response.","Retry the login to obtain a fresh refreshToken if the exchange reports it invalid/expired.","Verify the hardcoded Trae API origins are reachable from this machine (curl each origin).","If a schema change is indicated, update the extractJsonPath key lists to match Trae's current response.","Retry after a delay during Trae outages."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"for (const origin of traeApiOrigins()) {\n  try { const r = await fetch(origin, { method: 'HEAD' }); if (!r.ok) return false; }\n  catch { return false; }\n}\nreturn true;","typeGuard":null,"tryCatchPattern":"try {\n  const tokens = await fetchTraeExchangeToken(refreshToken, loginHost);\n} catch (e) {\n  if (e.message.startsWith('Trae ExchangeToken failed:')) {\n    // network error → retry with backoff; token invalid → re-run login; parse error → update extractJsonPath keys\n  } else throw e;\n}","preventionTips":["Exchange the refreshToken promptly after callback — long delays can expire it.","Health-check the hardcoded Trae API origins from the deployment environment.","Add per-origin backoff retries before declaring failure.","Track Trae response schema changes and keep extractJsonPath key lists current."],"tags":["network","oauth","token-exchange","endpoint-unreachable"],"backgroundTag":"endpoint-unreachable","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}