{"record":{"id":"571401b9ca0258ea","repo":"decolua/9router","slug":"failed-to-save-tokens","errorCode":null,"errorMessage":"Failed to save tokens","messagePattern":"Failed to save tokens","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/antigravity.js","lineNumber":218,"sourceCode":"      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        Authorization: `Bearer ${token}`,\n        \"X-User-Id\": userId,\n      },\n      body: JSON.stringify({\n        accessToken: tokens.access_token,\n        refreshToken: tokens.refresh_token,\n        expiresIn: tokens.expires_in,\n        scope: tokens.scope,\n        email: userInfo.email,\n        projectId: projectId, // Send projectId to server\n      }),\n    });\n\n    if (!response.ok) {\n      const error = await response.json();\n      throw new Error(error.error || \"Failed to save tokens\");\n    }\n\n    return await response.json();\n  }\n\n  /**\n   * Complete Antigravity OAuth flow\n   */\n  async connect() {\n    const spinner = createSpinner(\"Starting Antigravity OAuth...\").start();\n\n    try {\n      spinner.text = \"Starting local server...\";\n\n      // Start local server for callback\n      let callbackParams = null;\n      const { port, close } = await startLocalServer((params) => {\n        callbackParams = params;","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/antigravity.js#L200-L236","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const { server, token, userId } = getServerCredentials();\nif (!server || !token) throw new Error('CLI not authenticated with a 9router server — run login first');\nconst ping = await fetch(`${server}/api/health`).catch(() => null);\nif (!ping || !ping.ok) throw new Error(`Dashboard at ${server} is not reachable`);","typeGuard":"function hasServerCreds(c) { return c && typeof c.server === 'string' && c.server.startsWith('http') && typeof c.token === 'string' && c.token.length > 0; }","tryCatchPattern":"try {\n  await service.saveTokens(tokens, userInfo, projectId);\n} catch (e) {\n  if (e.message === 'Failed to save tokens' || /save tokens/i.test(e.message)) {\n    // re-login CLI, verify server URL, then retry save — Google tokens are still valid\n  } else throw e;\n}","preventionTips":["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"],"tags":["oauth","http-error","persistence","local-server"],"backgroundTag":"token-save-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}