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
- Retry the refresh — after losing the lease, the other owner likely stored a fresh credential; re-read the stored credential first
- Check for clock skew between processes sharing the store, since lease expiry uses wall-clock timestamps
- Reduce refresh latency (network issues) so renewal happens well within the lease TTL
- 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
- Keep refresh operations fast (avoid slow pre-work inside the lease) so renewals stay well within TTL
- Synchronize clocks (NTP) across machines sharing the credential store
- Avoid many processes force-refreshing the same credential simultaneously
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
- OAuth refresh ownership aborted by caller
- OAuth credential no longer exists for provider: ${provider}
- No credential with id=${id}
- session header changed during stats cleanup: ${session.path}
- Daemon ${spec.name} is already starting
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/bb12ef11555fc27c.
Report an issue: GitHub.