@@ -354,6 +354,11 @@ const codebaseMutex = new Mutex();
354354// subsequent callers wait and then re-check the flags.
355355const hotReloadMutex = new Mutex ( ) ;
356356
357+ // Guards extension hot-reload so parallel tool calls can't trigger
358+ // simultaneous Client rebuilds — only the first caller reloads,
359+ // subsequent callers wait then see the updated fingerprint/timestamp.
360+ const extHotReloadMutex = new Mutex ( ) ;
361+
357362/** Shared result from the hot-reload check — set by the winner, consumed by waiters. */
358363let hotReloadResult : CallToolResult | null = null ;
359364
@@ -480,22 +485,32 @@ function registerTool(tool: ToolDefinition): void {
480485 // Either condition triggers handleHotReload() which tells Host to
481486 // stop Client → build → spawn new Client. If build is already current,
482487 // the rebuild step is a fast no-op.
488+ //
489+ // Serialized via extHotReloadMutex so parallel tool calls can't
490+ // trigger simultaneous Client rebuilds. The first caller does the
491+ // reload; subsequent callers wait, then re-check and see the
492+ // updated fingerprint/timestamp.
483493 if ( ! isStandalone && config . explicitExtensionDevelopmentPath ) {
484- const stale = isBuildStale ( config . extensionBridgePath ) ;
485- const windowStartedAt = lifecycleService . debugWindowStartedAt ;
486- const buildNewerThanWindow = ! stale
487- && windowStartedAt !== undefined
488- && hasBuildChangedSinceWindowStart ( config . extensionBridgePath , windowStartedAt ) ;
489-
490- logger ( `[hot-reload] check: stale=${ stale } , buildNewerThanWindow=${ buildNewerThanWindow } , extDir=${ config . extensionBridgePath } ` ) ;
491-
492- if ( stale || buildNewerThanWindow ) {
493- const reason = stale ? 'source stale' : 'manual build detected' ;
494- logger ( `[tool:${ tool . name } ] Extension needs hot-reload (${ reason } ) — reloading…` ) ;
495- await lifecycleService . handleHotReload ( ) ;
496- writeExtSourceFingerprint ( config . extensionBridgePath ) ;
497- extensionHotReloadInfo = { builtAt : Date . now ( ) } ;
498- logger ( `[tool:${ tool . name } ] Hot-reload complete — reconnected` ) ;
494+ const extHrGuard = await extHotReloadMutex . acquire ( ) ;
495+ try {
496+ const stale = isBuildStale ( config . extensionBridgePath ) ;
497+ const windowStartedAt = lifecycleService . debugWindowStartedAt ;
498+ const buildNewerThanWindow = ! stale
499+ && windowStartedAt !== undefined
500+ && hasBuildChangedSinceWindowStart ( config . extensionBridgePath , windowStartedAt ) ;
501+
502+ logger ( `[hot-reload] check: stale=${ stale } , buildNewerThanWindow=${ buildNewerThanWindow } , extDir=${ config . extensionBridgePath } ` ) ;
503+
504+ if ( stale || buildNewerThanWindow ) {
505+ const reason = stale ? 'source stale' : 'manual build detected' ;
506+ logger ( `[tool:${ tool . name } ] Extension needs hot-reload (${ reason } ) — reloading…` ) ;
507+ await lifecycleService . handleHotReload ( ) ;
508+ writeExtSourceFingerprint ( config . extensionBridgePath ) ;
509+ extensionHotReloadInfo = { builtAt : Date . now ( ) } ;
510+ logger ( `[tool:${ tool . name } ] Hot-reload complete — reconnected` ) ;
511+ }
512+ } finally {
513+ extHrGuard . dispose ( ) ;
499514 }
500515 }
501516
0 commit comments