Skip to content

Commit ccf5470

Browse files
fix: separate retry flags for GET-SSE vs POST; forward send() options on retry
Two fixes from claude[bot] round-6 review: 1. Shared _authRetryInFlight between _startOrAuthSse() and send() created a race: if the fire-and-forget GET SSE gets 401 and sets the flag while awaiting onUnauthorized(), a concurrent POST send() that also gets 401 would see flag=true and throw ClientHttpAuthentication without ever attempting its own re-auth. The old _hasCompletedAuthFlow was only set in send() — I introduced the regression when adding 401 handling to _startOrAuthSse. Split into _authRetryInFlight (send path) and _sseAuthRetryInFlight (GET-SSE path). 2. Pre-existing: send() 401/403 retries called this.send(message) without forwarding the options parameter, dropping onresumptiontoken on the retried request. Added options to both call sites.
1 parent ce5703a commit ccf5470

File tree

1 file changed

+10
-9
lines changed

1 file changed

+10
-9
lines changed

packages/client/src/client/streamableHttp.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ export class StreamableHTTPClientTransport implements Transport {
141141
private _sessionId?: string;
142142
private _reconnectionOptions: StreamableHTTPReconnectionOptions;
143143
private _protocolVersion?: string;
144-
private _authRetryInFlight = false; // Circuit breaker: single retry per operation on 401
144+
private _authRetryInFlight = false; // Circuit breaker for send() 401 retry
145+
private _sseAuthRetryInFlight = false; // Circuit breaker for _startOrAuthSse() 401 retry — separate so concurrent GET/POST 401s don't interfere
145146
private _lastUpscopingHeader?: string; // Track last upscoping header to prevent infinite upscoping.
146147
private _serverRetryMs?: number; // Server-provided retry delay from SSE retry field
147148
private _reconnectionTimeout?: ReturnType<typeof setTimeout>;
@@ -218,8 +219,8 @@ export class StreamableHTTPClientTransport implements Transport {
218219
this._scope = scope;
219220
}
220221

221-
if (this._authProvider.onUnauthorized && !this._authRetryInFlight) {
222-
this._authRetryInFlight = true;
222+
if (this._authProvider.onUnauthorized && !this._sseAuthRetryInFlight) {
223+
this._sseAuthRetryInFlight = true;
223224
await this._authProvider.onUnauthorized({
224225
response,
225226
serverUrl: this._url,
@@ -230,7 +231,7 @@ export class StreamableHTTPClientTransport implements Transport {
230231
return this._startOrAuthSse(options);
231232
}
232233
await response.text?.().catch(() => {});
233-
if (this._authRetryInFlight) {
234+
if (this._sseAuthRetryInFlight) {
234235
throw new SdkError(SdkErrorCode.ClientHttpAuthentication, 'Server returned 401 after re-authentication', {
235236
status: 401
236237
});
@@ -243,7 +244,7 @@ export class StreamableHTTPClientTransport implements Transport {
243244
// 405 indicates that the server does not offer an SSE stream at GET endpoint
244245
// This is an expected case that should not trigger an error
245246
if (response.status === 405) {
246-
this._authRetryInFlight = false;
247+
this._sseAuthRetryInFlight = false;
247248
return;
248249
}
249250

@@ -253,10 +254,10 @@ export class StreamableHTTPClientTransport implements Transport {
253254
});
254255
}
255256

256-
this._authRetryInFlight = false;
257+
this._sseAuthRetryInFlight = false;
257258
this._handleSseStream(response.body, options, true);
258259
} catch (error) {
259-
this._authRetryInFlight = false;
260+
this._sseAuthRetryInFlight = false;
260261
this.onerror?.(error as Error);
261262
throw error;
262263
}
@@ -516,7 +517,7 @@ export class StreamableHTTPClientTransport implements Transport {
516517
});
517518
await response.text?.().catch(() => {});
518519
// Purposely _not_ awaited, so we don't call onerror twice
519-
return this.send(message);
520+
return this.send(message, options);
520521
}
521522
await response.text?.().catch(() => {});
522523
if (this._authRetryInFlight) {
@@ -564,7 +565,7 @@ export class StreamableHTTPClientTransport implements Transport {
564565
throw new UnauthorizedError();
565566
}
566567

567-
return this.send(message);
568+
return this.send(message, options);
568569
}
569570
}
570571

0 commit comments

Comments
 (0)