withRefreshingCredentials
@forge/monorepo / backend/src / withRefreshingCredentials
Function: withRefreshingCredentials()
withRefreshingCredentials(
resolver,refresher,options?):CredentialResolver
Defined in: backend/src/tools/credentials.ts:454
Wraps a resolver so an expiring credential is renewed before it is handed out.
A wrapper, like withCredentialAudit, and for the same reason: the eight shipped toolkits already resolve
per call, so they pick this up without a line changing. A toolkit that cached a credential at
construction would defeat it, which is why createGitHubToolkit and every sibling resolve inside call().
Time-driven, never 401-driven — AC-7
The obvious design is to refresh when the vendor returns 401. It is wrong, and worth stating plainly because it is what most integrations do:
A 401 is what a vendor returns for an expired token, a revoked grant, a token for the wrong tenant, and a scope the grant never had. Refreshing on 401 therefore turns a revoked grant into an infinite refresh loop against the vendor's token endpoint, and turns a missing scope into a refresh that succeeds and a call that fails again identically. Neither is diagnosable from the outside.
Time is the only signal that means what it says: a token with an expiry in the past is expired, and nothing else is inferred from it. A 401 on a freshly-refreshed token is a real error and is surfaced as one.
One refresh, not N — AC-2
Twenty concurrent tool calls hitting an expired token must produce one refresh. Refresh endpoints rate limit, and — worse — several vendors invalidate the previous refresh token when one is used, so N concurrent refreshes race to invalidate each other and log the deployment out permanently.
The in-flight promise is stored before the first await, so a second caller entering the function synchronously after the first still finds it.
Parameters
resolver
refresher
options?
RefreshingResolverOptions = {}