{"record":{"id":"d0226672917dcba0","repo":"can1357/oh-my-pi","slug":"no-oauth-credential-available-for-provider-prov","errorCode":null,"errorMessage":"No OAuth credential available for provider: ${provider}","messagePattern":"No OAuth credential available for provider: (.+?)","errorType":"exception","errorClass":"AIError.MissingApiKeyError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/auth-retry.ts","lineNumber":337,"sourceCode":" * sibling rotation stops when it yields a credential identity\n * (`credentialId ?? accessToken`) or bearer already attempted in this turn.\n * All OAuth attempts share the {@link AUTH_RETRY_MAX_ATTEMPTS} ceiling.\n * Non-auth errors propagate immediately. Use this instead of hand-rolled\n * `getOAuthAccess` + fetch flows so 401s and usage-limits rotate credentials\n * instead of failing the call.\n */\nexport async function withOAuthAccess<T>(\n\tstorage: OAuthAccessSource,\n\tprovider: string,\n\tattempt: (access: OAuthAccess) => Promise<T>,\n\topts?: WithOAuthAccessOptions,\n): Promise<T> {\n\tconst isAuthError = opts?.isAuthError ?? isAuthRetryableError;\n\tconst { sessionId, signal } = opts ?? {};\n\n\tlet lastAccess = opts?.seed ?? (await storage.getOAuthAccess(provider, sessionId, { signal }));\n\tif (!lastAccess) {\n\t\tthrow new AIError.MissingApiKeyError(\n\t\t\tprovider,\n\t\t\topts?.missingAccessMessage ?? `No OAuth credential available for provider: ${provider}`,\n\t\t);\n\t}\n\n\tconst attemptedBearers = new Set([lastAccess.accessToken]);\n\tconst attemptedCredentialIdentities = new Set([oauthCredentialIdentity(lastAccess)]);\n\tlet attemptCount = 1;\n\tlet legacyAuthSwitchUsed = false;\n\tlet refreshedCurrent = false;\n\tlet tokenRefreshReplayUsed = false;\n\tlet attemptResult = await runOAuthAttempt(lastAccess, attempt, isAuthError);\n\tif (attemptResult.ok) return attemptResult.result;\n\n\tlet lastError = attemptResult.error;\n\twhile (true) {\n\t\tlet next: OAuthAccess | undefined;\n\t\tif (signal?.aborted || attemptCount >= AUTH_RETRY_MAX_ATTEMPTS) break;","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/auth-retry.ts#L319-L355","documentation":"withOAuthAccess runs an auth-protected operation with automatic credential rotation. Before the first attempt it fetches the current OAuth access (or uses opts.seed); if none exists it throws MissingApiKeyError because there is no credential to authenticate with at all. This is thrown up front, not after a failed request.","triggerScenarios":"Calling withOAuthAccess(storage, provider, ...) (directly or via helpers like searchCodex/searchGemini) when storage.getOAuthAccess(provider, sessionId) returns undefined and no opts.seed is provided — i.e. no OAuth credential is stored for that provider/session.","commonSituations":"User never ran the OAuth login flow for the provider; credential was logged out or removed from storage; wrong provider string passed (typo or runtime-registered provider without stored creds); using a sessionId that has no session-scoped credential.","solutions":["Run the OAuth login flow for the provider before calling (storage login / loginOAuth) so a credential exists","Check the provider string matches the provider you authenticated against (exact spelling)","If credentials are session-scoped, ensure the credential exists for that sessionId or omit sessionId to use the default","Register a clear logout/re-login handler so expired/removed credentials prompt re-auth"],"exampleFix":"// before\nconst result = await withOAuthAccess(storage, \"codex\", callApi);\n// after\nif (!(await storage.getOAuthAccess(\"codex\", sessionId))) {\n\tawait storage.loginOAuth(\"codex\", { onAuth: saveAuth }); // obtain credential first\n}\nconst result = await withOAuthAccess(storage, \"codex\", callApi);","handlingStrategy":"validation","validationCode":"const access = await storage.getOAuthAccess(provider, sessionId);\nif (!access) throw new Error(`Run OAuth login for ${provider} first`);","typeGuard":null,"tryCatchPattern":"try {\n\treturn await withOAuthAccess(storage, provider, attempt);\n} catch (error) {\n\tif (error instanceof AIError.MissingApiKeyError) {\n\t\tawait triggerLoginFlow(provider); // prompt re-auth\n\t}\n\tthrow error;\n}","preventionTips":["Check login state at startup and prompt for OAuth login before issuing requests","Use consistent exact provider id strings (central constant, not literals)","Handle logout events by clearing dependent in-flight work"],"tags":["auth","oauth","missing-credential","login-required"],"backgroundTag":"missing-oauth-credential","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}