@@ -115,11 +115,15 @@ export async function getServiceAuthToken(agent: Agent, lxm?: string): Promise<s
115115 return cached . token ;
116116 }
117117
118- // Request new service auth token from PDS
119- const response = await agent . com . atproto . server . getServiceAuth ( {
120- aud : CHIVE_SERVICE_DID ,
121- lxm,
122- } ) ;
118+ // Request new service auth token from PDS.
119+ // The first call against a given PDS can fail with `use_dpop_nonce` when the
120+ // OAuth client's stored nonce is empty or stale for that origin. The
121+ // @atproto /oauth-client-browser fetch handler is supposed to auto-retry once,
122+ // but in practice the retry can leak through as a thrown error -- the
123+ // response *does* return a fresh `DPoP-Nonce`, so a second call succeeds.
124+ // Wrapping in a single explicit retry guarantees the user-visible request
125+ // succeeds when the only obstacle is the nonce-priming step.
126+ const response = await callGetServiceAuthWithNonceRetry ( agent , lxm ) ;
123127
124128 const { token } = response . data as ServiceAuthResponse ;
125129
@@ -133,6 +137,40 @@ export async function getServiceAuthToken(agent: Agent, lxm?: string): Promise<s
133137 return token ;
134138}
135139
140+ async function callGetServiceAuthWithNonceRetry (
141+ agent : Agent ,
142+ lxm ?: string
143+ ) : Promise < { data : ServiceAuthResponse } > {
144+ try {
145+ return await agent . com . atproto . server . getServiceAuth ( {
146+ aud : CHIVE_SERVICE_DID ,
147+ lxm,
148+ } ) ;
149+ } catch ( error ) {
150+ if ( isNonceMismatchError ( error ) ) {
151+ serviceAuthLogger . debug ( 'getServiceAuth hit DPoP nonce mismatch; retrying once' , { lxm } ) ;
152+ return await agent . com . atproto . server . getServiceAuth ( {
153+ aud : CHIVE_SERVICE_DID ,
154+ lxm,
155+ } ) ;
156+ }
157+ throw error ;
158+ }
159+ }
160+
161+ function isNonceMismatchError ( error : unknown ) : boolean {
162+ if ( ! error || typeof error !== 'object' ) return false ;
163+ const message = ( error as { message ?: string } ) . message ?? '' ;
164+ const data = ( error as { data ?: unknown } ) . data ;
165+ const dataError =
166+ data && typeof data === 'object' ? ( data as { error ?: string } ) . error : undefined ;
167+ return (
168+ dataError === 'use_dpop_nonce' ||
169+ / u s e _ d p o p _ n o n c e / i. test ( message ) ||
170+ / d p o p .* n o n c e .* m i s m a t c h / i. test ( message )
171+ ) ;
172+ }
173+
136174/**
137175 * Clear all cached service auth tokens.
138176 *
0 commit comments