decolua/9router · error · Error
Failed to save tokens
Error message
Failed to save tokens
What it means
Thrown by AntigravityService.saveTokens when the POST to the local 9router server (`${server}/api/cli/providers/antigravity`) returns a non-2xx status. The server's JSON `error` field is preferred in the message, with the generic string as fallback. The OAuth flow itself succeeded — only persisting credentials into the dashboard failed, so no provider account gets registered.
Source
Thrown at src/lib/oauth/services/antigravity.js:218
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
"X-User-Id": userId,
},
body: JSON.stringify({
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresIn: tokens.expires_in,
scope: tokens.scope,
email: userInfo.email,
projectId: projectId, // Send projectId to server
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || "Failed to save tokens");
}
return await response.json();
}
/**
* Complete Antigravity OAuth flow
*/
async connect() {
const spinner = createSpinner("Starting Antigravity OAuth...").start();
try {
spinner.text = "Starting local server...";
// Start local server for callback
let callbackParams = null;
const { port, close } = await startLocalServer((params) => {
callbackParams = params;View on GitHub (pinned to 90b52e06ff)
Solutions
- Read the server-provided `error` message in the thrown Error — it names the exact rejection reason
- Re-authenticate the CLI with the server (re-login) so the Bearer token in getServerCredentials() is fresh
- Verify the `server` URL in your CLI config points at the running 9router dashboard instance
- Retry after confirming the dashboard is up — the Google tokens may still be in your terminal output and only the save step needs repeating
Defensive patterns
Strategy: try-catch
Validate before calling
const { server, token, userId } = getServerCredentials();
if (!server || !token) throw new Error('CLI not authenticated with a 9router server — run login first');
const ping = await fetch(`${server}/api/health`).catch(() => null);
if (!ping || !ping.ok) throw new Error(`Dashboard at ${server} is not reachable`); Type guard
function hasServerCreds(c) { return c && typeof c.server === 'string' && c.server.startsWith('http') && typeof c.token === 'string' && c.token.length > 0; } Try / catch
try {
await service.saveTokens(tokens, userInfo, projectId);
} catch (e) {
if (e.message === 'Failed to save tokens' || /save tokens/i.test(e.message)) {
// re-login CLI, verify server URL, then retry save — Google tokens are still valid
} else throw e;
} Prevention
- Re-authenticate the CLI if its session token is older than the dashboard JWT TTL
- Keep CLI-configured server URL in sync with the actual dashboard host/port
- Avoid restarting the dashboard mid-OAuth-flow
- Check the dashboard logs for the /api/cli/providers/antigravity rejection reason
When it happens
Trigger: The dashboard API rejects the save: expired/invalid CLI Bearer token, wrong `server` URL from getServerCredentials(), missing X-User-Id, duplicate account rejected server-side, or the server is unreachable/restarting.
Common situations: CLI connected to a different/older 9router server than the one that issued the CLI token; JWT session expired after long OAuth round-trip; server restarted during the Google login; misconfigured server URL in CLI config.
Related errors
- Failed to save tokens
- Failed to save tokens
- HTTP ${response.status}: ${errorMsg}
- Failed to reset Codex limit
- Failed to load Codex reset credits
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/571401b9ca0258ea.
Report an issue: GitHub.