@@ -196,6 +196,13 @@ export class NodeStreamableHTTPServerTransport implements Transport {
196196 * @param parsedBody - Optional pre-parsed body from body-parser middleware
197197 */
198198 async handleRequest ( req : IncomingMessage & { auth ?: AuthInfo } , res : ServerResponse , parsedBody ?: unknown ) : Promise < void > {
199+ // Fail fast before delegating: once getRequestListener is invoked, a
200+ // rejected dispatch is committed to `res` as an empty 500 before the
201+ // rejection can surface, leaving the caller unable to shape its own
202+ // error response. Checking up front rejects with nothing written to
203+ // `res`. The check lives on the wrapped transport (single source).
204+ this . _webStandardTransport . assertNotReused ( ) ;
205+
199206 // Store context for this request to pass through auth and parsedBody
200207 // We need to intercept the request creation to attach this context
201208 const authInfo = req . auth ;
@@ -223,14 +230,15 @@ export class NodeStreamableHTTPServerTransport implements Transport {
223230 // including proper SSE streaming support
224231 await handler ( req , res ) ;
225232
226- // getRequestListener absorbs a rejected dispatch into a generic 500
227- // response, which would swallow the wrapped transport's single-use
228- // throw. Re-raise it so the documented behavior (reusing a stateless
229- // transport across requests throws at the call site) holds for the
230- // Node adapter too. Other dispatch errors keep the existing
231- // 500-response behavior. Matched by code, not `instanceof`, so the
232- // check also holds if bundling ever yields two copies of the error
233- // class.
233+ // Backstop for concurrent calls racing the up-front check (the
234+ // single-use flag is set inside the wrapped handleRequest, after the
235+ // request conversion): getRequestListener absorbs a rejected dispatch
236+ // into a generic 500, which would swallow the single-use throw.
237+ // Re-raise it so the documented behavior (reusing a stateless
238+ // transport across requests throws at the call site) holds on this
239+ // path too. Other dispatch errors keep the existing 500-response
240+ // behavior. Matched by code, not `instanceof`, so the check also
241+ // holds if bundling ever yields two copies of the error class.
234242 if ( dispatchError instanceof Error && ( dispatchError as { code ?: unknown } ) . code === SdkErrorCode . StatelessTransportReuse ) {
235243 throw dispatchError ;
236244 }
0 commit comments