decolua/9router · error · Error

"Trae callback missing loginHost"

Error message

"Trae callback missing loginHost"

What it means

Thrown by parseTraeCallback when the callback lacks any login-host field among loginHost, login_host, LoginHost, host, or consoleHost. The loginHost is needed to build API origins for subsequent ExchangeToken/GetUserInfo calls; note the library intentionally does NOT trust arbitrary hosts from the callback for API calls (SSRF guard), but a host field is still required by the flow.

Source

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

function parseTraeCallback(raw) {
  const text = String(raw || "").trim();
  let queryStr = text;
  if (text.includes("?")) queryStr = text.slice(text.indexOf("?") + 1);
  if (text.startsWith("#")) queryStr = text.slice(1);
  const params = Object.fromEntries(new URLSearchParams(queryStr));
  const pick = (keys) => {
    for (const k of keys) { const v = params[k]; if (v && String(v).trim()) return String(v).trim(); }
    return null;
  };
  const err = pick(["error", "error_code", "errorCode"]);
  if (err) {
    const desc = pick(["error_description", "error_desc", "message"]);
    throw new Error(desc ? `Trae auth failed: ${err} (${desc})` : `Trae auth failed: ${err}`);
  }
  const refreshToken = pick(["refreshToken", "refresh_token", "RefreshToken"]);
  if (!refreshToken) throw new Error("Trae callback missing refreshToken");
  const loginHost = pick(["loginHost", "login_host", "LoginHost", "host", "consoleHost"]);
  if (!loginHost) throw new Error("Trae callback missing loginHost");
  const cloudideToken = pick(["x-cloudide-token", "xCloudideToken", "accessToken", "access_token", "token"]);
  return { refreshToken, loginHost, cloudideToken };
}

// Allowed API origins for ExchangeToken/GetUserInfo — hardcoded HTTPS allowlist only.
// loginHost from the callback is intentionally NOT honored (SSRF guard: a callback
// attacker could otherwise point this at internal hosts/cloud metadata).
function traeApiOrigins() {
  return [...TRAE_CONFIG.apiOrigins];
}

// POST ExchangeToken {ClientID, RefreshToken, ClientSecret, UserID} → {Result:{AccessToken,RefreshToken,ExpiresAt}}
async function fetchTraeExchangeToken(refreshToken, cloudideToken) {
  const body = JSON.stringify({
    ClientID: TRAE_CONFIG.clientId,
    RefreshToken: refreshToken,
    ClientSecret: TRAE_CONFIG.clientSecret,
    UserID: "",

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Dump the raw callback parameters to see exactly what Trae returned.
  2. If the field was renamed, extend the pick() key list in parseTraeCallback with the new name.
  3. Re-run the login flow to get an unmodified callback payload.
  4. Verify no reverse proxy or browser extension is stripping query parameters.

Example fix

// before
const loginHost = pick(["loginHost", "login_host", "LoginHost", "host", "consoleHost"]);
// after
const loginHost = pick(["loginHost", "login_host", "LoginHost", "host", "consoleHost", "apiHost"]); // add renamed key
Defensive patterns

Strategy: validation

Validate before calling

const params = new URLSearchParams(callbackUrl.split('?')[1] || '');
const hasHost = ['loginHost', 'login_host', 'LoginHost', 'host', 'consoleHost'].some(k => params.get(k)?.trim());
if (!hasHost) throw new Error('Callback has no loginHost; re-run Trae login');

Try / catch

try {
  const creds = parseTraeCallback(callbackUrl);
} catch (e) {
  if (e.message === 'Trae callback missing loginHost') {
    // dump raw params, re-run login, or extend accepted key names after a Trae schema change
  } else throw e;
}

Prevention

When it happens

Trigger: Trae's success redirect omits the login host parameter entirely — schema change on their side, sanitized callback URL, or a redirect chain that dropped params.

Common situations: Trae renames/removes the host param in a server update; manual URL pasting loses the parameter; intermediary proxy normalizes away unknown query keys.

Related errors


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