can1357/oh-my-pi · error · AIError.OAuthError

OAuth credential no longer exists for provider: ${provider}

Error message

OAuth credential no longer exists for provider: ${provider}

What it means

Thrown as AIError.OAuthError with kind 'token-refresh' when the credential that was about to be refreshed no longer exists in storage — it was deleted between the caller's read and the refresh request. The library cannot refresh a row that is gone, so it fails definitively rather than silently recreating it.

Source

Thrown at packages/ai/src/auth-storage.ts:5263

					// are legitimately short-lived); the next resolve simply treats it as
					// due for refresh again instead of rejecting a token we just minted.
					if (Date.now() < result.credential.expires) return result.credential;
				} else if (Date.now() + OAUTH_REFRESH_SKEW_MS < result.credential.expires) {
					// Reloaded (not refreshed by us) credential — match #refreshOAuthCredential's
					// freshness contract: a reload within the refresh skew still counts as
					// needing refresh, so returning it here would make the final candidate pass
					// refresh the same row again and replay the token we just failed on.
					return result.credential;
				}
				throw new AIError.OAuthError(
					`OAuth refresh did not produce a usable credential for provider: ${provider}`,
					{
						kind: "token-refresh",
						provider,
					},
				);
			}
			throw new AIError.OAuthError(`OAuth credential no longer exists for provider: ${provider}`, {
				kind: "token-refresh",
				provider,
			});
		}
		return this.#requestOAuthCredentialRefresh(provider, credential, credentialId, signal);
	}

	async #requestOAuthCredentialRefresh(
		provider: Provider,
		credential: OAuthCredential,
		credentialId: number | undefined,
		signal?: AbortSignal,
	): Promise<OAuthCredentials> {
		let refreshPromise: Promise<OAuthCredentials>;
		// Caller override > store-level hook > local per-provider refresh.
		// `RemoteAuthCredentialStore` exposes the hook so a broker-backed gateway
		// routes refresh through the broker without explicit wiring.
		const storeRefresh = this.#store.refreshOAuthCredential?.bind(this.#store);

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-run the OAuth login flow — the credential must be re-established before any request can succeed
  2. Coordinate logout/credential removal across processes so it doesn't race in-flight requests
  3. Catch this (kind 'token-refresh') and re-fetch credentials from storage before retrying
  4. If it happens repeatedly, check for another process or job deleting credentials (cleanup scripts, multi-instance setups)

Example fix

// before
const access = await withOAuthAccess(storage, "codex", call); // thrown mid-refresh
// after
try {
	return await withOAuthAccess(storage, "codex", call);
} catch (error) {
	if (error instanceof AIError.OAuthError && error.kind === "token-refresh") {
		if (!(await storage.getOAuthAccess("codex"))) await reLogin("codex");
		return withOAuthAccess(storage, "codex", call);
	}
	throw error;
}
Defensive patterns

Strategy: type-guard

Validate before calling

const access = await storage.getOAuthAccess(provider);
if (!access) await reLogin(provider); // row gone before refresh

Type guard

function isCredentialGoneError(e: unknown): e is AIError.OAuthError {
	return e instanceof AIError.OAuthError &&
		e.kind === "token-refresh" &&
		e.message.includes("no longer exists");
}

Try / catch

try {
	return await withOAuthAccess(storage, provider, attempt);
} catch (error) {
	if (isCredentialGoneError(error)) await reLogin(provider);
	throw error;
}

Prevention

When it happens

Trigger: Calling the refresh path (e.g. getOAuthApiKey triggering a needed refresh, or #requestOAuthCredentialRefresh by credentialId) after another code path — logout, removeCredential, another process — deleted the credential row for that provider.

Common situations: User logged out in another window/process while a request was in flight; concurrent session cleanup removing the credential; rotating credentials in one tab while another tab refreshes the old one.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/1c7fadd5a380e108. Report an issue: GitHub.