@@ -73,21 +73,42 @@ const openSession = async (
7373 label : string ,
7474 recordSession : ( sessionId : string ) => void ,
7575) : Promise < string > => {
76- const initialized = await postJson ( mcpUrl , bearer , {
77- jsonrpc : "2.0" as const ,
78- id : "initialize" ,
79- method : "initialize" ,
80- params : {
81- protocolVersion : PROTOCOL_VERSION ,
82- capabilities : { } ,
83- clientInfo : { name : `executor-e2e-cap-eviction-${ label } ` , version : "0.0.1" } ,
84- } ,
85- } ) ;
86- const sessionId = initialized . headers . get ( "mcp-session-id" ) ;
87- if ( ! sessionId ) {
76+ // The platform can reset a session Durable Object while its initialize
77+ // is in flight, and the server answers that with the documented restart
78+ // envelope (503, -32001, "MCP session is restarting, please retry") — the
79+ // same contract a streamable-http client follows: same request, after the
80+ // advertised delay. Treat it as transient here instead of failing the
81+ // scenario on a retryable platform blip.
82+ const RESTART_ATTEMPTS = 8 ;
83+ const RESTART_DELAY_MS = 250 ;
84+ let minted : { readonly response : Response ; readonly sessionId : string } | undefined ;
85+ for ( let attempt = 0 ; attempt < RESTART_ATTEMPTS ; attempt += 1 ) {
86+ const response = await postJson ( mcpUrl , bearer , {
87+ jsonrpc : "2.0" as const ,
88+ id : "initialize" ,
89+ method : "initialize" ,
90+ params : {
91+ protocolVersion : PROTOCOL_VERSION ,
92+ capabilities : { } ,
93+ clientInfo : { name : `executor-e2e-cap-eviction-${ label } ` , version : "0.0.1" } ,
94+ } ,
95+ } ) ;
96+ const candidate = response . headers . get ( "mcp-session-id" ) ;
97+ if ( candidate !== null && candidate . length > 0 ) {
98+ minted = { response, sessionId : candidate } ;
99+ break ;
100+ }
101+ const body = await response . text ( ) . catch ( ( ) => "" ) ;
102+ const isRestart = response . status === 503 && body . includes ( "MCP session is restarting" ) ;
103+ if ( ! isRestart ) break ;
104+ if ( attempt === RESTART_ATTEMPTS - 1 ) break ;
105+ await new Promise ( ( resolve ) => setTimeout ( resolve , RESTART_DELAY_MS ) ) ;
106+ }
107+ if ( ! minted ) {
88108 // oxlint-disable-next-line executor/no-error-constructor -- boundary: e2e setup precondition.
89109 throw new Error ( `openSession (${ label } ): no mcp-session-id header` ) ;
90110 }
111+ const { response : initialized , sessionId } = minted ;
91112 // Recorded the moment the id exists — BEFORE the body read and status
92113 // assertion below, either of which can throw with the session already live
93114 // on the server. The cleanup finalizer needs the id on every one of those
0 commit comments