{"record":{"id":"7d7750bf54416df0","repo":"mastra-ai/mastra","slug":"credential-storage-is-not-available","errorCode":null,"errorMessage":"Credential storage is not available","messagePattern":"Credential storage is not available","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"mastracode/factory/src/routes/oauth.ts","lineNumber":231,"sourceCode":"}: {\n  ctx: CredentialContext;\n  provider: string;\n  scope: LoginCredentialScope | undefined;\n  credentials: OAuthCredentials;\n  authStorage: AuthStorage | undefined;\n  onCredentialsChanged: (tenant: { orgId: string; userId?: string }) => void;\n}): Promise<void> {\n  const authProviderId = getAuthProviderId(provider);\n  if (ctx.mode === 'tenant') {\n    const tenant = { orgId: ctx.orgId, ...(scope === 'org' ? {} : { userId: ctx.userId }) };\n    await ctx.storage.setCredential(tenant, authProviderId, {\n      type: 'oauth',\n      ...credentials,\n    });\n    onCredentialsChanged(tenant);\n    return;\n  }\n  if (!authStorage) throw new Error('Credential storage is not available');\n  authStorage.set(authProviderId, { type: 'oauth', ...credentials });\n}\n\nasync function readJsonBody(c: Context): Promise<Record<string, unknown>> {\n  try {\n    const body = (await c.req.json()) as unknown;\n    return body && typeof body === 'object' ? (body as Record<string, unknown>) : {};\n  } catch {\n    return {};\n  }\n}\n\nexport interface OAuthRoutesDeps extends RouteDependencies {\n  /** File-backed credential store; used in local (no-auth) mode. */\n  authStorage?: AuthStorage;\n  /** Tenant credential domain handle; absent in local (no-DB) mode. */\n  modelCredentials?: ModelCredentialsStorage;\n  /** Notifies the host after tenant credentials change so caches can be dropped. */","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/routes/oauth.ts#L213-L249","documentation":"persistOAuthCredential stores OAuth tokens either in the tenant in-memory store or, as a fallback, in a configured authStorage. When neither exists (authStorage is null) the credential would be silently lost, so the function throws instead. It's a configuration error: the factory was assembled without credential storage.","triggerScenarios":"Completing an OAuth flow (callback/token exchange route) when the factory was built without an authStorage implementation and the credential isn't handled by the in-memory tenant path.","commonSituations":"Deployments where persistent credential storage wasn't wired in the factory options (e.g. serverless or ephemeral environments with no storage adapter configured); forgetting to pass authStorage when calling assembleFactoryApiRoutes/routes; tests hitting OAuth routes with a stubbed factory config.","solutions":["Configure an authStorage implementation when assembling the factory routes so OAuth credentials can be persisted.","Verify the factory/bootstrap code actually passes authStorage into the routes options (it may be conditionally omitted).","If persistence is intentionally unsupported in your environment, pre-handle credentials via the in-memory tenant path or disable OAuth routes.","In tests, provide a fake authStorage implementing get/set to satisfy the flow."],"exampleFix":"// before\nassembleFactoryApiRoutes({ /* no authStorage */ });\n// after\nassembleFactoryApiRoutes({\n  authStorage: createAuthStorage({ driver: 'db', url: process.env.DATABASE_URL }),\n});","handlingStrategy":"validation","validationCode":"function assertCredentialStorageConfigured(opts: { authStorage?: { set(id: string, v: unknown): void } | null }) {\n  if (!opts.authStorage) throw new Error('authStorage must be configured for OAuth routes');\n}","typeGuard":"function hasAuthStorage(o: unknown): o is { authStorage: { set: (id: string, v: unknown) => void } } {\n  return typeof o === 'object' && o !== null && 'authStorage' in o && (o as any).authStorage != null;\n}","tryCatchPattern":"try {\n  await completeOAuthFlow(...);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Credential storage is not available') {\n    logger.error('OAuth credential dropped: configure authStorage in factory options');\n    return Response.json({ error: 'server misconfiguration: credential storage missing' }, { status: 500 });\n  }\n  throw e;\n}","preventionTips":["Always wire authStorage when assembling factory routes in production.","Add a startup check that fails fast if OAuth routes are enabled without authStorage.","Use the same storage adapter across environments so staging/prod parity holds.","In tests, provide an in-memory authStorage stub implementing set/get."],"tags":["oauth","configuration","storage","missing-dependency"],"backgroundTag":"missing-env-var","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}