{"record":{"id":"db9b9848ce302b40","repo":"decolua/9router","slug":"failed-to-save-tokens-db9b98","errorCode":null,"errorMessage":"Failed to save tokens","messagePattern":"Failed to save tokens","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/openai.js","lineNumber":84,"sourceCode":"    const response = await fetch(`${server}/api/cli/providers/openai`, {\n      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        idToken: tokens.id_token,\n        scope: tokens.scope,\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 OpenAI OAuth flow\n   */\n  async connect() {\n    const spinner = createSpinner(\"Starting OpenAI OAuth...\").start();\n\n    try {\n      spinner.text = \"Starting local server...\";\n\n      // Authenticate and get authorization code\n      const { code, codeVerifier, redirectUri } = await this.authenticate(\n        \"OpenAI\",\n        this.buildOpenAIAuthUrl.bind(this)","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/openai.js#L66-L102","documentation":"Thrown by OpenAIService.saveTokens() when the POST of the freshly obtained tokens to the dashboard server (`${server}/api/cli/providers/openai`) returns a non-2xx status. It surfaces the server's own `error` field when present, else the generic fallback message. The OAuth exchange itself succeeded; only persisting the tokens to the local server failed.","triggerScenarios":"connect() -> saveTokens(tokens) posts to the server with Bearer token and X-User-Id headers from getServerCredentials(); the server replies !response.ok with a JSON body — auth rejected (expired/invalid JWT or wrong user), server not running at the configured URL, the OpenAI provider endpoint missing, or a validation rejection of the payload.","commonSituations":"Dashboard server not started or URL/port misconfigured (getServerCredentials pointing at the wrong host); CLI session/JWT expired; user id mismatch between CLI login and server; server version older than the endpoint the CLI calls; database write failure on the server side.","solutions":["Read the thrown `error.error` value — it names the server-side reason (401 auth vs 404 route vs 400 validation).","Re-login to refresh the Bearer token / credentials used by getServerCredentials(); expired sessions are the top cause.","Confirm the dashboard server is running and that `server` in getServerCredentials() points to the correct host:port.","Verify server and CLI versions match so the /api/cli/providers/openai endpoint exists and accepts the payload.","Check server logs for the failing request to see the underlying persistence error."],"exampleFix":"// before\nif (!response.ok) {\n  const error = await response.json();\n  throw new Error(error.error || \"Failed to save tokens\");\n}\n// after (keep status, survive non-JSON bodies)\nif (!response.ok) {\n  let msg = `Failed to save tokens (HTTP ${response.status})`;\n  try { const e = await response.json(); if (e && e.error) msg = `${msg}: ${e.error}`; } catch {}\n  throw new Error(msg);\n}","handlingStrategy":"try-catch","validationCode":"// Before saveTokens, verify server credentials are present and reachable:\nconst { server, token, userId } = getServerCredentials();\nif (!server || !token) throw new Error(\"Not logged in: missing server credentials\");\nawait fetch(`${server}/api/health`).catch(() => { throw new Error(`Server unreachable at ${server}`); });","typeGuard":"function isSaveResponse(json) {\n  return json != null && typeof json === \"object\" && !json.error;\n}","tryCatchPattern":"try {\n  await service.saveTokens(tokens);\n} catch (err) {\n  if (err.message === \"Failed to save tokens\" || /Failed to save tokens/.test(err.message)) {\n    // re-authenticate CLI session, confirm dashboard server is running, then retry\n  } else throw err;\n}","preventionTips":["Start the dashboard server before running CLI connect flows.","Re-login when the CLI session ages out; expired JWTs cause 401s on save.","Confirm the configured server URL/port matches where the dashboard actually runs.","Keep CLI and server versions in sync so the providers/openai endpoint exists.","Save tokens soon after exchange so access/refresh data is complete and valid."],"tags":["oauth","openai","token-persistence","http","server"],"backgroundTag":"token-save-request-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}