decolua/9router · error · Error
`Cline token exchange failed: ${error}`
Error message
`Cline token exchange failed: ${error}` What it means
When the base64-encoded-JSON shortcut in cline.js throws (see error 197), the code falls back to POSTing the authorization code to Cline's tokenExchangeUrl. If that HTTP fallback returns non-ok, this error is thrown with the raw response body. This is the terminal failure of the Cline token exchange — the code could not be converted into tokens either locally or via the API.
Source
Thrown at src/lib/oauth/providers/cline.js:40
if (lastBrace === -1) throw new Error("No JSON found in decoded code");
const tokenData = JSON.parse(decoded.substring(0, lastBrace + 1));
return {
access_token: tokenData.accessToken,
refresh_token: tokenData.refreshToken,
email: tokenData.email,
firstName: tokenData.firstName,
lastName: tokenData.lastName,
expires_at: tokenData.expiresAt,
};
} catch (e) {
const response = await fetch(config.tokenExchangeUrl, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify({ grant_type: "authorization_code", code, client_type: "extension", redirect_uri: redirectUri }),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Cline token exchange failed: ${error}`);
}
const data = await response.json();
return {
access_token: data.data?.accessToken || data.accessToken,
refresh_token: data.data?.refreshToken || data.refreshToken,
email: data.data?.userInfo?.email || "",
expires_at: data.data?.expiresAt || data.expiresAt,
};
}
},
mapTokens: (tokens) => ({
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expires_at
? Math.floor((new Date(tokens.expires_at).getTime() - Date.now()) / 1000)
: 3600,
email: tokens.email,
providerSpecificData: { firstName: tokens.firstName, lastName: tokens.lastName },View on GitHub (pinned to 90b52e06ff)
Solutions
- Read the appended body for the upstream error (invalid_grant etc.) and fix that cause.
- invalid_grant: restart the OAuth flow — codes are single-use and short-lived.
- Verify redirect_uri passed to exchangeToken equals the callback_url/redirect_uri used in buildAuthUrl.
- Check Cline service status / update tokenExchangeUrl if the API moved.
- Retry after a 5xx with a fresh authorization flow.
Example fix
// before: code reused from a previous completed flow await exchangeToken(config, usedCode, redirectUri); // 400 invalid_grant // after: always exchange a freshly delivered code exactly once const tokens = await exchangeToken(config, freshCallbackCode, redirectUri);
Defensive patterns
Strategy: try-catch
Validate before calling
const code = new URL(callbackUrl).searchParams.get('code');
if (!code || code.length < 8) throw new Error('invalid or missing code from Cline callback — skip exchange');
if (usedClineCodes.has(code)) throw new Error('Cline code already exchanged'); Type guard
function isPlausibleClineCode(v) {
return typeof v === 'string' && v.trim().length >= 8 && !/\s/.test(v);
} Try / catch
try {
const tokens = await cline.exchangeToken(config, code, redirectUri);
// use tokens
} catch (err) {
if (String(err.message).startsWith('Cline token exchange failed:')) {
const body = err.message.slice('Cline token exchange failed:'.length);
if (/invalid_grant|expired/i.test(body)) startFreshClineLogin();
else if (/5\d\d|unavailable/i.test(body)) scheduleRetryWithBackoff();
else reportToUser(body); // 4xx config/contract issue
} else throw err;
} Prevention
- Consume each Cline authorization code exactly once; block callback refreshes.
- Keep CLINE_CONFIG.callbackUrl/redirect_uri identical across buildAuthUrl and exchangeToken.
- Watch Cline's API status; 5xx bodies here are usually upstream outages.
- Update tokenExchangeUrl promptly when Cline changes its extension API.
- Exchange promptly after the callback to avoid code expiry.
When it happens
Trigger: Fallback exchange with: an expired/replayed authorization code, a redirect_uri not matching the one in buildAuthUrl, Cline API rejecting client_type 'extension' or returning 4xx/5xx, or the code param being garbage (neither base64 JSON nor a valid code).
Common situations: Cline service outage or API contract change, user refreshing the callback (code reuse), wrong callback_url/redirect_uri in CLINE_CONFIG, network proxy returning error pages, or expired codes from slow manual flows.
Related errors
- `Token exchange failed: ${error}`
- `Token exchange failed: ${error}`
- `Token exchange failed: ${error}`
- Token exchange failed: ${error}
- OIDC token exchange failed (${res.status})
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/8d18f5fda32632e1.
Report an issue: GitHub.