can1357/oh-my-pi · error · AIError.MissingApiKeyError
No OAuth credential available for provider: ${provider}
Error message
No OAuth credential available for provider: ${provider} What it means
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.
Source
Thrown at packages/ai/src/auth-retry.ts:337
* sibling rotation stops when it yields a credential identity
* (`credentialId ?? accessToken`) or bearer already attempted in this turn.
* All OAuth attempts share the {@link AUTH_RETRY_MAX_ATTEMPTS} ceiling.
* Non-auth errors propagate immediately. Use this instead of hand-rolled
* `getOAuthAccess` + fetch flows so 401s and usage-limits rotate credentials
* instead of failing the call.
*/
export async function withOAuthAccess<T>(
storage: OAuthAccessSource,
provider: string,
attempt: (access: OAuthAccess) => Promise<T>,
opts?: WithOAuthAccessOptions,
): Promise<T> {
const isAuthError = opts?.isAuthError ?? isAuthRetryableError;
const { sessionId, signal } = opts ?? {};
let lastAccess = opts?.seed ?? (await storage.getOAuthAccess(provider, sessionId, { signal }));
if (!lastAccess) {
throw new AIError.MissingApiKeyError(
provider,
opts?.missingAccessMessage ?? `No OAuth credential available for provider: ${provider}`,
);
}
const attemptedBearers = new Set([lastAccess.accessToken]);
const attemptedCredentialIdentities = new Set([oauthCredentialIdentity(lastAccess)]);
let attemptCount = 1;
let legacyAuthSwitchUsed = false;
let refreshedCurrent = false;
let tokenRefreshReplayUsed = false;
let attemptResult = await runOAuthAttempt(lastAccess, attempt, isAuthError);
if (attemptResult.ok) return attemptResult.result;
let lastError = attemptResult.error;
while (true) {
let next: OAuthAccess | undefined;
if (signal?.aborted || attemptCount >= AUTH_RETRY_MAX_ATTEMPTS) break;View on GitHub (pinned to 9690622007)
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
Example fix
// before
const result = await withOAuthAccess(storage, "codex", callApi);
// after
if (!(await storage.getOAuthAccess("codex", sessionId))) {
await storage.loginOAuth("codex", { onAuth: saveAuth }); // obtain credential first
}
const result = await withOAuthAccess(storage, "codex", callApi); Defensive patterns
Strategy: validation
Validate before calling
const access = await storage.getOAuthAccess(provider, sessionId);
if (!access) throw new Error(`Run OAuth login for ${provider} first`); Try / catch
try {
return await withOAuthAccess(storage, provider, attempt);
} catch (error) {
if (error instanceof AIError.MissingApiKeyError) {
await triggerLoginFlow(provider); // prompt re-auth
}
throw error;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- HTTP ${response.status} resuming MCP SSE stream: auth refres
- Security scan authentication identity mismatch
- Security scans require a stored OAuth account for ${provider
- Security OAuth credential ${requestedCredentialId} is not av
- Failed to open auth database at '${dbPath}' after ${maxAttem
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/d0226672917dcba0.
Report an issue: GitHub.