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
- Log the full response body to see what Qoder actually returned
- Update/align the QODER_IDE_VERSION and client headers with the currently supported Qoder CLI version
- Re-generate the PAT and retry the exchange with a fresh credential
- 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
- Log unexpected 200 bodies to detect Qoder API schema changes early
- Keep QODER_IDE_VERSION / Cosy headers current with the official CLI
- Re-generate the PAT if a valid-looking exchange repeatedly returns no token
- Report/pin the Qoder endpoint version in config so upgrades are deliberate
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
- onboardUser done but no project_id in response
- qoder: model_config for "${qoderKey}" not yet known (run a m
- qoder PAT exchange failed: ${res.status} ${text.slice(0, 200
- aes key must be 16 bytes, got ${keyBytes.length}
- cosy: user id is empty
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/9922385411217054.
Report an issue: GitHub.