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

OAuth refresh ownership was lost before persistence

Error message

OAuth refresh ownership was lost before persistence

What it means

While refreshing, the lease owner periodically renews its durable refresh lease (renewCredentialRefreshLease) to keep ownership until persistence. If a renewal returns false — meaning the lease was taken over or expired before the new token was persisted — the library throws ConfigurationError because persisting under lost ownership could clobber another process's fresher credential.

Source

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

			const serialized = serializeCredential(provider, current);
			if (!serialized) return { credential: current, refreshed: false, removed: false };

			let stopLeaseRenewal = false;
			let leaseRenewalError: unknown;
			const leaseRenewalStopped = Promise.withResolvers<void>();
			const leaseRenewal =
				leasedCredentialId !== undefined
					? (async () => {
							while (!stopLeaseRenewal) {
								await Promise.race([Bun.sleep(OAUTH_REFRESH_LEASE_RENEW_MS), leaseRenewalStopped.promise]);
								if (stopLeaseRenewal) return;
								const renewed = this.#store.renewCredentialRefreshLease?.(
									leasedCredentialId,
									owner,
									Date.now() + OAUTH_REFRESH_LEASE_TTL_MS,
								);
								if (!renewed) {
									throw new AIError.ConfigurationError("OAuth refresh ownership was lost before persistence");
								}
							}
						})().catch(error => {
							leaseRenewalError = error;
						})
					: undefined;
			const refreshAbort = new AbortController();
			const refreshTimeout = setTimeout(() => {
				refreshAbort.abort(
					new AIError.OAuthError(`OAuth token refresh timed out for provider: ${provider}`, {
						kind: "timeout",
						provider,
					}),
				);
			}, options.refreshTimeoutMs ?? OAUTH_REFRESH_OPERATION_TIMEOUT_MS);

			let refreshed: OAuthCredentials;
			try {

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry the refresh — after losing the lease, the other owner likely stored a fresh credential; re-read the stored credential first
  2. Check for clock skew between processes sharing the store, since lease expiry uses wall-clock timestamps
  3. Reduce refresh latency (network issues) so renewal happens well within the lease TTL
  4. If persistent, verify the store's tryAcquire/renewCredentialRefreshLease implementation honors owner semantics

Example fix

// before
await refreshAndPersist(); // throws if lease lost mid-flight
// after
try {
	await refreshAndPersist();
} catch (error) {
	if (error instanceof AIError.ConfigurationError && /ownership was lost/.test(error.message)) {
		return storage.getOAuthAccess(provider); // peer likely persisted a fresh token
	}
	throw error;
}
Defensive patterns

Strategy: retry

Try / catch

try {
	await doRefresh();
} catch (error) {
	if (error instanceof AIError.ConfigurationError && /ownership was lost/.test(error.message)) {
		const fresh = await storage.getOAuthAccess(provider); // peer likely persisted
		if (fresh) return fresh;
	}
	throw error;
}

Prevention

When it happens

Trigger: The OAuth refresh takes longer than OAUTH_REFRESH_LEASE_TTL_MS and the renewal call fails to extend the lease — typically because another process acquired the lease after expiry, or the backing store lost/reset the lease row mid-refresh.

Common situations: Very slow token endpoint or network causing the refresh to exceed lease TTL; multiple machines sharing the same credential database racing to refresh; a store implementation whose lease renewal misbehaves or whose clock is skewed.

Related errors


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