decolua/9router · error · Error
qoder PAT exchange failed: ${res.status} ${text.slice(0, 200
Error message
qoder PAT exchange failed: ${res.status} ${text.slice(0, 200)} What it means
exchangeJobToken swaps a Qoder personal access token (pt-...) for a short-lived job token (jt-...) via a plain JSON POST. On a non-OK response it throws with the HTTP status and first 200 chars of the body. This means Qoder's token-exchange endpoint rejected the request or the PAT.
Source
Thrown at open-sse/services/qoderModels.js:85
const res = await proxyAwareFetch(
QODER_JOB_TOKEN_EXCHANGE_URL,
{
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.
*/View on GitHub (pinned to 90b52e06ff)
Solutions
- Verify the stored Qoder credential is a valid, unexpired PAT starting with pt- and re-generate it in the Qoder dashboard if in doubt
- Check the response body embedded in the message (200 chars) for the precise Qoder error reason
- If 429, back off and retry after the rate-limit window
- Confirm proxy options are correct and the endpoint is reachable from your network
Example fix
// before
const { jobToken } = await exchangeJobToken(pat);
// after
try {
var { jobToken } = await exchangeJobToken(pat);
} catch (e) {
if (/exchange failed: 401/.test(e.message)) {
pat = await promptUserForNewPat();
var { jobToken } = await exchangeJobToken(pat);
} else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// validate the credential shape before exchanging
if (typeof pat !== 'string' || !pat.startsWith('pt-')) {
throw new Error('Store a valid Qoder personal access token (pt-...) first.');
} Type guard
function isQoderPat(t) { return typeof t === 'string' && /^pt-[A-Za-z0-9_-]+$/.test(t); } Try / catch
try {
const { jobToken } = await exchangeJobToken(pat, proxyOptions);
} catch (e) {
if (/exchange failed: 401/.test(e.message)) {
pat = await promptForNewPat(); // re-create PAT in Qoder dashboard
return exchangeJobToken(pat, proxyOptions);
}
if (/exchange failed: 429/.test(e.message)) { await backoff(); return exchangeJobToken(pat, proxyOptions); }
throw e;
} Prevention
- Regenerate Qoder PATs before their expiry and update the stored credential
- Keep the Cosy-Version / client headers aligned with the supported Qoder CLI version
- Read the 200-char body in the message — it states why Qoder rejected the exchange
- Test the exchange once at connection setup so a bad PAT fails early, not mid-session
When it happens
Trigger: The POST to Qoder's job-token exchange URL returns non-2xx — 401 for an invalid/revoked/misspelled PAT, 403 for a disabled account, 429 rate limit, or 5xx from Qoder.
Common situations: User pasted an expired or wrong-type token (not a pt- token); PAT revoked in the Qoder dashboard; corporate proxy intercepting the exchange; Qoder API outage.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Failed to fetch image: ${res.status}
- Google translate fetch failed: ${res.status}
- Google TTS failed: ${res.status}
- MiniMax TTS error (${res.status})
- loadCodeAssist failed: HTTP ${response.status} ${errorText.s
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/e519f4da881582ff.
Report an issue: GitHub.