{"record":{"id":"a8ac76cd7b064094","repo":"can1357/oh-my-pi","slug":"oauth-refresh-ownership-aborted-by-caller","errorCode":null,"errorMessage":"OAuth refresh ownership aborted by caller","messagePattern":"OAuth refresh ownership aborted by caller","errorType":"exception","errorClass":"AIError.AbortError","httpStatus":null,"severity":"info","filePath":"packages/ai/src/auth-storage.ts","lineNumber":2463,"sourceCode":"\n\t/**\n\t * Refresh one stored OAuth credential under durable row ownership.\n\t */\n\tasync refreshStoredOAuthCredential<T extends OAuthCredential = OAuthCredential>(\n\t\tprovider: string,\n\t\toptions: StoredOAuthRefreshOptions<T>,\n\t): Promise<StoredOAuthRefreshResult<T>> {\n\t\tconst refreshSkewMs = options.refreshSkewMs ?? OAUTH_REFRESH_SKEW_MS;\n\t\tconst hasDurableLease =\n\t\t\t!!this.#store.tryAcquireCredentialRefreshLease &&\n\t\t\t!!this.#store.getCredentialRefreshLeaseExpiresAt &&\n\t\t\t!!this.#store.releaseCredentialRefreshLease &&\n\t\t\t!!this.#store.renewCredentialRefreshLease;\n\t\tconst owner = crypto.randomUUID();\n\t\tlet leasedCredentialId: number | undefined;\n\n\t\twhile (hasDurableLease) {\n\t\t\tif (options.signal?.aborted) throw new AIError.AbortError(\"OAuth refresh ownership aborted by caller\");\n\t\t\tconst rows = this.#store.listAuthCredentials(provider);\n\t\t\tthis.#setStoredCredentials(\n\t\t\t\tprovider,\n\t\t\t\trows.map(row => ({ id: row.id, credential: row.credential })),\n\t\t\t);\n\t\t\tconst row = rows.find(\n\t\t\t\tentry =>\n\t\t\t\t\tentry.credential.type === \"oauth\" &&\n\t\t\t\t\t(options.credentialId === undefined || entry.id === options.credentialId),\n\t\t\t);\n\t\t\tif (row?.credential.type !== \"oauth\") {\n\t\t\t\treturn { credential: undefined, refreshed: false, removed: false };\n\t\t\t}\n\t\t\tconst current = options.credentialFromRow(row.credential);\n\t\t\tif (!current) {\n\t\t\t\treturn { credential: undefined, refreshed: false, removed: false };\n\t\t\t}\n\t\t\tconst currentIsFresh = Date.now() + refreshSkewMs < current.expires;","sourceCodeStart":2445,"sourceCodeEnd":2481,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/auth-storage.ts#L2445-L2481","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait storage.refreshOAuthCredential(provider, { signal: controller.signal });\n// after\ntry {\n\tawait storage.refreshOAuthCredential(provider, { signal: controller.signal });\n} catch (error) {\n\tif (error instanceof AIError.AbortError) return null; // caller cancelled\n\tthrow error;\n}","handlingStrategy":"try-catch","validationCode":"if (signal?.aborted) return null; // don't start a refresh that will be aborted","typeGuard":null,"tryCatchPattern":"try {\n\treturn await storage.refreshOAuthCredential(provider, { signal });\n} catch (error) {\n\tif (error instanceof AIError.AbortError) return null; // cancelled by caller\n\tthrow error;\n}","preventionTips":["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"],"tags":["abort","cancellation","oauth","lease"],"backgroundTag":"operation-aborted","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}