{"record":{"id":"54c30017f64e1817","repo":"can1357/oh-my-pi","slug":"oauth-provider-provider-does-not-support-toke","errorCode":null,"errorMessage":"OAuth provider \"${provider}\" does not support token refresh","messagePattern":"OAuth provider \"(.+?)\" does not support token refresh","errorType":"exception","errorClass":"AIError.OAuthError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/auth-storage.ts","lineNumber":5289,"sourceCode":"\tasync #requestOAuthCredentialRefresh(\n\t\tprovider: Provider,\n\t\tcredential: OAuthCredential,\n\t\tcredentialId: number | undefined,\n\t\tsignal?: AbortSignal,\n\t): Promise<OAuthCredentials> {\n\t\tlet refreshPromise: Promise<OAuthCredentials>;\n\t\t// Caller override > store-level hook > local per-provider refresh.\n\t\t// `RemoteAuthCredentialStore` exposes the hook so a broker-backed gateway\n\t\t// routes refresh through the broker without explicit wiring.\n\t\tconst storeRefresh = this.#store.refreshOAuthCredential?.bind(this.#store);\n\t\tconst overrideRefresh = this.#refreshOAuthCredentialOverride ?? storeRefresh;\n\t\tif (overrideRefresh && credentialId !== undefined) {\n\t\t\trefreshPromise = overrideRefresh(provider, credentialId, credential, signal);\n\t\t} else {\n\t\t\tconst customProvider = getOAuthProvider(provider);\n\t\t\tif (customProvider) {\n\t\t\t\tif (!customProvider.refreshToken) {\n\t\t\t\t\tthrow new AIError.OAuthError(`OAuth provider \"${provider}\" does not support token refresh`, {\n\t\t\t\t\t\tkind: \"configuration\",\n\t\t\t\t\t\tprovider,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\trefreshPromise = customProvider.refreshToken(credential, signal);\n\t\t\t} else {\n\t\t\t\trefreshPromise = refreshOAuthToken(provider as OAuthProvider, credential, signal);\n\t\t\t}\n\t\t}\n\t\t// Bound the refresh so a slow/hanging token endpoint cannot stall credential selection.\n\t\t// Caller-driven abort jumps the gun on the timeout — the agent's ESC must\n\t\t// take priority over the floor timeout.\n\t\tconst cancellation = Promise.withResolvers<never>();\n\t\tlet onAbort: (() => void) | undefined;\n\t\tconst timeout = setTimeout(\n\t\t\t() =>\n\t\t\t\tcancellation.reject(\n\t\t\t\t\tnew AIError.OAuthError(`OAuth token refresh timed out for provider: ${provider}`, {","sourceCodeStart":5271,"sourceCodeEnd":5307,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/auth-storage.ts#L5271-L5307","documentation":"When refreshing via a runtime-registered custom OAuth provider, the library checks customProvider.refreshToken. If the provider is registered but has no refreshToken implementation, token refresh cannot proceed and the library throws AIError.OAuthError with kind 'configuration'. This is a provider-registration gap, not a network or token problem.","triggerScenarios":"A stored OAuth credential for a custom (runtime-registered) provider expires and the library attempts refresh, but getOAuthProvider(provider).refreshToken is undefined — the provider was registered with login only, or with refresh support removed.","commonSituations":"Custom provider registered without a refreshToken function; forcing a refresh (overrideRefresh absent, credentialId present) on a provider that only supports initial login; upstream changed the provider registration and dropped refresh support.","solutions":["Add a refreshToken implementation to your custom OAuth provider registration","If the provider cannot refresh, plan for re-login when the access token expires instead of forcing refresh","Pass overrideRefresh (a provider-capable refresh function) when triggering refresh for this credential","Verify the correct provider id — a builtin provider with refresh support may be shadowed by an incomplete custom registration of the same id"],"exampleFix":"// before\nregisterOAuthProvider({ id: \"myprov\", login: loginFlow }); // no refreshToken\n// after\nregisterOAuthProvider({\n\tid: \"myprov\",\n\tlogin: loginFlow,\n\trefreshToken: async (credential, signal) => refreshViaProvider(credential.refresh, signal),\n});","handlingStrategy":"validation","validationCode":"const custom = getOAuthProvider(provider);\nif (custom && !custom.refreshToken) {\n\tthrow new Error(`${provider} lacks refreshToken; schedule re-login instead`);\n}","typeGuard":null,"tryCatchPattern":"try {\n\treturn await getCredentialWithRefresh(provider);\n} catch (error) {\n\tif (error instanceof AIError.OAuthError && error.kind === \"configuration\") {\n\t\tawait reLogin(provider); // cannot refresh; log in again\n\t}\n\tthrow error;\n}","preventionTips":["Always implement refreshToken when registering a custom OAuth provider with expiring tokens","Unit-test refresh for every registered provider","Don't shadow builtin provider ids with incomplete custom registrations"],"tags":["oauth","configuration","custom-provider","token-refresh"],"backgroundTag":"provider-refresh-not-supported","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}