decolua/9router · error · Error

qoder PAT exchange returned no job token

Error message

qoder PAT exchange returned no job token

What it means

If the Qoder token-exchange endpoint returns HTTP 200 but the JSON body has no token field, exchangeJobToken throws this error. It indicates the exchange succeeded at transport level but Qoder did not issue a job token — an unexpected/changed response payload.

Source

Thrown at open-sse/services/qoderModels.js:88

      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
        "User-Agent": "qodercli/1.0.0",
        "Cosy-Version": QODER_IDE_VERSION,
        "Cosy-ClientType": QODER_CLIENT_TYPE,
      },
      body: JSON.stringify({ personal_token: pat }),
      signal,
    },
    proxyOptions,
  );
  if (!res.ok) {
    const text = await res.text().catch(() => "");
    throw new Error(`qoder PAT exchange failed: ${res.status} ${text.slice(0, 200)}`);
  }
  const data = await res.json();
  if (!data.token) throw new Error("qoder PAT exchange returned no job token");

  let expiresAt = Date.now() + PAT_DEFAULT_TTL_MS;
  if (data.expires_at) {
    const parsed = Date.parse(data.expires_at);
    if (!Number.isNaN(parsed)) expiresAt = parsed;
  } else if (typeof data.expires_in === "number" && data.expires_in > 0) {
    expiresAt = Date.now() + data.expires_in;
  }
  return { jobToken: data.token, jobRefreshToken: data.refresh_token || "", expiresAt };
}

/**
 * Resolve the Qoder userId for a job token (needed for COSY signing).
 * Returns "" on any failure — callers fall back to the stored userId.
 */
async function fetchUserIdForJobToken(jobToken, proxyOptions = null, signal = null) {
  try {
    const res = await proxyAwareFetch(

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Log the full response body to see what Qoder actually returned
  2. Update/align the QODER_IDE_VERSION and client headers with the currently supported Qoder CLI version
  3. Re-generate the PAT and retry the exchange with a fresh credential
  4. If Qoder changed the field name, update exchangeJobToken in open-sse/services/qoderModels.js to read the new token field
Defensive patterns

Strategy: try-catch

Validate before calling

// after res.ok, narrow the payload before using it
const data = await res.json();
if (!data || typeof data.token !== 'string') {
  throw new Error('Qoder exchange payload missing token: ' + JSON.stringify(data).slice(0, 200));
}

Type guard

function hasJobToken(d) { return Boolean(d) && typeof d.token === 'string' && d.token.startsWith('jt-'); }

Try / catch

try {
  var { jobToken } = await exchangeJobToken(pat, proxyOptions);
} catch (e) {
  if (e.message.includes('no job token')) {
    console.error('Qoder exchange body:', e.message); // capture the actual payload
    pat = await promptForNewPat();
    var { jobToken } = await exchangeJobToken(pat, proxyOptions);
  } else throw e;
}

Prevention

When it happens

Trigger: Qoder returns 200 with JSON lacking data.token (e.g. an error envelope with 200 status, API schema change, or account state that yields no token).

Common situations: Qoder API version drift (Cosy-Version header no longer matches a supported client); account in a partially provisioned state; an intermediary returning a 200 HTML/JSON body that parses but has no token.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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