can1357/oh-my-pi · info · AIError.AbortError
OAuth refresh ownership aborted by caller
Error message
OAuth refresh ownership aborted by caller
What it means
During the durable-lease based OAuth refresh loop, the library checks the caller's AbortSignal before each iteration. If the caller aborted the refresh (e.g. request cancelled or timeout), it throws AbortError labeled 'OAuth refresh ownership aborted by caller'. This is a cooperative-cancellation signal, not a refresh failure.
Source
Thrown at packages/ai/src/auth-storage.ts:2463
/**
* Refresh one stored OAuth credential under durable row ownership.
*/
async refreshStoredOAuthCredential<T extends OAuthCredential = OAuthCredential>(
provider: string,
options: StoredOAuthRefreshOptions<T>,
): Promise<StoredOAuthRefreshResult<T>> {
const refreshSkewMs = options.refreshSkewMs ?? OAUTH_REFRESH_SKEW_MS;
const hasDurableLease =
!!this.#store.tryAcquireCredentialRefreshLease &&
!!this.#store.getCredentialRefreshLeaseExpiresAt &&
!!this.#store.releaseCredentialRefreshLease &&
!!this.#store.renewCredentialRefreshLease;
const owner = crypto.randomUUID();
let leasedCredentialId: number | undefined;
while (hasDurableLease) {
if (options.signal?.aborted) throw new AIError.AbortError("OAuth refresh ownership aborted by caller");
const rows = this.#store.listAuthCredentials(provider);
this.#setStoredCredentials(
provider,
rows.map(row => ({ id: row.id, credential: row.credential })),
);
const row = rows.find(
entry =>
entry.credential.type === "oauth" &&
(options.credentialId === undefined || entry.id === options.credentialId),
);
if (row?.credential.type !== "oauth") {
return { credential: undefined, refreshed: false, removed: false };
}
const current = options.credentialFromRow(row.credential);
if (!current) {
return { credential: undefined, refreshed: false, removed: false };
}
const currentIsFresh = Date.now() + refreshSkewMs < current.expires;View on GitHub (pinned to 9690622007)
Solutions
- Expected behavior when cancelling: catch AIError.AbortError and treat the refresh as cancelled
- If unintentional, audit what aborts your signal (timeout too short, request teardown) and extend the timeout
- Retry the refresh later; the lease system ensures another owner may have refreshed already
- Before retrying, re-read the stored credential — it may now be fresh from the other lease owner
Example fix
// before
await storage.refreshOAuthCredential(provider, { signal: controller.signal });
// after
try {
await storage.refreshOAuthCredential(provider, { signal: controller.signal });
} catch (error) {
if (error instanceof AIError.AbortError) return null; // caller cancelled
throw error;
} Defensive patterns
Strategy: try-catch
Validate before calling
if (signal?.aborted) return null; // don't start a refresh that will be aborted
Try / catch
try {
return await storage.refreshOAuthCredential(provider, { signal });
} catch (error) {
if (error instanceof AIError.AbortError) return null; // cancelled by caller
throw error;
} Prevention
- Use generous timeouts for refreshes that may wait on a peer's lease
- Reuse one AbortController per logical request, not a global one
- After cancellation, re-read storage — the peer lease owner may have refreshed already
When it happens
Trigger: Passing an AbortSignal to a stored-OAuth-refresh operation (via StoredOAuthRefreshOptions.signal) and aborting it while the lease loop is running — including while polling for a lease held by another process/peer.
Common situations: Request timeout or user cancellation aborting a token refresh that is waiting on a refresh lease; shutdown paths cancelling in-flight refreshes; another process holds the lease so the poll loop runs long enough for the caller's timeout to fire.
Related errors
- Login cancelled
- Request was aborted
- Auth broker request aborted
- OAuth refresh ownership was lost before persistence
- Request was aborted.
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/a8ac76cd7b064094.
Report an issue: GitHub.