decolua/9router · error
Token exchange failed: ${error}
Error message
Token exchange failed: ${error} What it means
OpenAI-specific override of the generic token exchange: exchangeOpenAICode() POSTs the authorization code, client_id, redirect_uri, and code_verifier to OPENAI_CONFIG.tokenUrl, and throws when the response is not ok. The raw response body (typically `invalid_grant` or `invalid_client` JSON) is included in the message. Raised on the path exchangeOpenAICode <- tokens/connect flow.
Source
Thrown at src/lib/oauth/services/openai.js:54
async exchangeOpenAICode(code, redirectUri, codeVerifier) {
const response = await fetch(OPENAI_CONFIG.tokenUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: new URLSearchParams({
grant_type: "authorization_code",
client_id: OPENAI_CONFIG.clientId,
code: code,
redirect_uri: redirectUri,
code_verifier: codeVerifier,
}),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Token exchange failed: ${error}`);
}
return await response.json();
}
/**
* Save OpenAI tokens to server
*/
async saveTokens(tokens) {
const { server, token, userId } = getServerCredentials();
const response = await fetch(`${server}/api/cli/providers/openai`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
"X-User-Id": userId,
},View on GitHub (pinned to 90b52e06ff)
Solutions
- Run the full connect() flow again to get a fresh code and exchange it immediately; the code is single-use and short-lived.
- Verify the embedded body text for the exact OAuth error and address it (invalid_grant -> fresh code; invalid_client -> client_id).
- Ensure redirect_uri is the exact same localhost URL used in the authorize step (same port) — don't restart the CLI between authorize and exchange.
- Check outbound HTTPS access to OpenAI's token endpoint (proxy/VPN can return an HTML block page).
- Update the CLI — a stale bundled OPENAI_CONFIG.clientId can be rejected by OpenAI.
Example fix
// before
const tokens = await this.exchangeOpenAICode(code, redirectUri, codeVerifier);
// after (fresh flow per attempt, immediate exchange)
const { code, codeVerifier, redirectUri } = await this.authenticate("OpenAI", this.buildOpenAIAuthUrl.bind(this));
const tokens = await this.exchangeOpenAICode(code, redirectUri, codeVerifier); // exchange right away, no restart in between Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-exchange checks:
if (!code) throw new Error("No authorization code to exchange");
if (!redirectUri.startsWith("http://localhost:")) throw new Error("redirect_uri must match the authorize-time localhost URL"); Type guard
function isOpenAITokenResponse(json) {
return json != null && typeof json === "object" && typeof json.access_token === "string";
} Try / catch
try {
const tokens = await service.exchangeOpenAICode(code, redirectUri, codeVerifier);
} catch (err) {
if (err.message.startsWith("Token exchange failed:")) {
if (err.message.includes("invalid_grant")) { /* restart full flow for a fresh code */ }
else if (err.message.includes("invalid_client")) { /* update CLI / check OPENAI_CONFIG.clientId */ }
else throw err;
} else throw err;
} Prevention
- Do the authorize->exchange without restarts so redirect_uri port stays identical.
- Exchange the fresh code immediately; don't cache it across runs.
- Keep the CLI updated so the bundled OpenAI client_id/config stays valid.
- Verify outbound access to OpenAI's token endpoint through proxies/VPNs.
When it happens
Trigger: connect() -> authenticate() succeeds, then exchangeOpenAICode(code, redirectUri, codeVerifier) gets !response.ok from OpenAI's token endpoint — the one-time code expired or was redeemed, code_verifier doesn't match the challenge, redirect_uri (the ephemeral localhost:port/callback) differs from the authorize request, or the registered OpenAI client_id is rejected.
Common situations: Waiting too long between browser approval and exchange (code TTL); re-running after a partial failure reuses the old code; a different port bound on restart changes redirect_uri; OpenAI rotating/invalidating the client_id packaged with the CLI; network egress blocked to auth.openai.com.
Related errors
- `Token exchange failed: ${error}`
- `Token exchange failed: ${error}`
- xAI token exchange failed: ${error}
- Token exchange failed: ${error}
- Token exchange failed: ${error}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/1f0f9e4d1abe439f.
Report an issue: GitHub.