{"record":{"id":"13f9b7c6da00beec","repo":"coleam00/Archon","slug":"stored-openai-credential-has-no-refresh-token","errorCode":null,"errorMessage":"Stored OpenAI credential has no refresh token.","messagePattern":"Stored OpenAI credential has no refresh token\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/credentials/openai-oauth.ts","lineNumber":299,"sourceCode":"    }),\n    'exchange',\n    signal\n  );\n  return credentialsFromTokenResponse(json, 'exchange');\n}\n\n/**\n * Refresh an OpenAI subscription credential directly (same public client id).\n * Preserves `id_token` (and `refresh`) when the refresh response omits them —\n * the reason this does NOT go through Pi's `getOAuthApiKey`, which would\n * rebuild the blob from scratch and drop the id_token on every rotation.\n */\nexport async function refreshOpenAiOAuthCredentials(\n  creds: OAuthCredentials\n): Promise<OpenAiOAuthCredentials> {\n  const refresh = typeof creds.refresh === 'string' ? creds.refresh : '';\n  if (!refresh) {\n    throw new Error('Stored OpenAI credential has no refresh token.');\n  }\n  const json = await postTokenRequest(\n    new URLSearchParams({\n      grant_type: 'refresh_token',\n      client_id: OPENAI_CLIENT_ID,\n      refresh_token: refresh,\n    }),\n    'refresh'\n  );\n  return credentialsFromTokenResponse(json, 'refresh', creds);\n}\n\n/**\n * Mint a usable bearer from a stored OpenAI credential blob, refreshing first\n * when expired. Same contract as Pi's `getOAuthApiKey` (`{ newCredentials,\n * apiKey } | null`) so the store's shared rotation/resave logic applies\n * unchanged. Throws when a needed refresh fails.\n *","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/credentials/openai-oauth.ts#L281-L317","documentation":"refreshOpenAiOAuthCredentials reads the refresh token from the stored credential before contacting OpenAI. If the stored OAuthCredentials has no string `refresh` field, it throws immediately instead of sending a doomed request — a credential without a refresh token can never be refreshed and must be re-created via a fresh login.","triggerScenarios":"Calling refreshOpenAiOAuthCredentials (directly or via next()/mintOpenAiOAuthApiKey) with a credential whose `refresh` property is undefined, non-string, or empty.","commonSituations":"Credential loaded from an old database row created before refresh tokens were stored; credentials constructed by hand from just an access token; partial deserialization of stored JSON that dropped the refresh field.","solutions":["Force a fresh OAuth login (authorization-code + PKCE) to obtain a new credential with a refresh token.","Inspect the stored credential row/JSON to confirm the refresh field is present and non-empty; re-import if truncated.","Check the code path that saved the credential — earlier versions or error paths may have omitted refresh_token.","If the credential was migrated, backfill the refresh token from the original login response or re-authenticate."],"exampleFix":"// before\nconst fresh = await refreshOpenAiOAuthCredentials({ access: token }); // throws: no refresh\n// after: guard and re-authenticate when refresh is absent\nif (typeof creds.refresh !== 'string' || !creds.refresh) {\n  creds = await runOpenAiLoginFlow();\n}\nconst fresh = await refreshOpenAiOAuthCredentials(creds);","handlingStrategy":"type-guard","validationCode":"function canRefresh(c) { return c && typeof c.refresh === 'string' && c.refresh.length > 0; }\nif (!canRefresh(creds)) throw new Error('stored OpenAI credential lacks refresh token; re-authentication required');","typeGuard":"function isOpenAiRefreshable(c: OAuthCredentials): c is OAuthCredentials & { refresh: string } {\n  return typeof c.refresh === 'string' && c.refresh.length > 0;\n}","tryCatchPattern":"try {\n  return await refreshOpenAiOAuthCredentials(creds);\n} catch (e) {\n  if (e.message === 'Stored OpenAI credential has no refresh token.') {\n    return await promptFreshOpenAiLogin(); // user-visible re-auth\n  }\n  throw e;\n}","preventionTips":["Validate stored credentials at load time: fail fast if refresh is missing so re-auth happens early, not mid-run.","Never write credential rows without refresh_token; enforce at the persistence boundary.","Include refresh in credential export/import/backup formats.","Re-authenticate proactively before long operations if the credential predates refresh-token storage."],"tags":["oauth","openai","refresh-token","auth"],"backgroundTag":"oauth-refresh-token-missing","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}