Skip to main content

withRefreshingCredentials

@forge/monorepo


@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

CredentialResolver

refresher

CredentialRefresher

options?

RefreshingResolverOptions = {}

Returns

CredentialResolver