decolua/9router · error · Error

`Trae ExchangeToken failed: ${lastErr}`

Error message

`Trae ExchangeToken failed: ${lastErr}`

What it means

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.

Source

Thrown at src/lib/oauth/providers/trae.js:151

      if (!res.ok) { lastErr = `${url} HTTP ${res.status}`; continue; }
      let data; try { data = JSON.parse(text); } catch { lastErr = `${url} invalid JSON`; continue; }
      const accessToken = extractJsonPath(data, [
        ["Result", "AccessToken"], ["Result", "accessToken"], ["result", "access_token"], ["accessToken"],
      ]);
      if (!accessToken) {
        const msg = extractJsonPath(data, [["message"], ["msg"], ["error"], ["Result", "Message"]]) || "missing AccessToken";
        lastErr = `${url} ${msg}`;
        continue;
      }
      return {
        accessToken,
        refreshToken: extractJsonPath(data, [["Result", "RefreshToken"], ["result", "refresh_token"], ["refreshToken"]]) || refreshToken,
        expiresIn: null, // ExchangeToken returns ExpiresAt (absolute), converted below
        expiresAt: extractJsonPath(data, [["Result", "ExpiresAt"], ["Result", "expiresAt"], ["result", "expires_at"], ["expiresAt"]]),
      };
    } catch (e) { lastErr = `${url} ${e.message}`; }
  }
  throw new Error(`Trae ExchangeToken failed: ${lastErr}`);
}

// POST GetUserInfo with x-cloudide-token → identity fields used by SOLO common_params.
async function fetchTraeUserInfo(accessToken) {
  for (const origin of traeApiOrigins()) {
    const url = `${origin.replace(/\/$/, "")}${TRAE_CONFIG.getUserInfoPath}`;
    try {
      const res = await fetch(url, {
        method: "POST",
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json",
          "User-Agent": TRAE_CONFIG.userAgent,
          "x-cloudide-token": accessToken,
        },
        body: JSON.stringify({}),
      });
      if (!res.ok) continue;

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Read lastErr in the message: a fetch exception means network/unreachable; an HTTP/parse message points at the API response.
  2. Retry the login to obtain a fresh refreshToken if the exchange reports it invalid/expired.
  3. Verify the hardcoded Trae API origins are reachable from this machine (curl each origin).
  4. If a schema change is indicated, update the extractJsonPath key lists to match Trae's current response.
  5. Retry after a delay during Trae outages.
Defensive patterns

Strategy: retry

Validate before calling

for (const origin of traeApiOrigins()) {
  try { const r = await fetch(origin, { method: 'HEAD' }); if (!r.ok) return false; }
  catch { return false; }
}
return true;

Try / catch

try {
  const tokens = await fetchTraeExchangeToken(refreshToken, loginHost);
} catch (e) {
  if (e.message.startsWith('Trae ExchangeToken failed:')) {
    // network error → retry with backoff; token invalid → re-run login; parse error → update extractJsonPath keys
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/15bcd1f2b3ffaa82. Report an issue: GitHub.