@@ -298,6 +298,7 @@ export async function applyInitialEnvironmentSelection(
298298 } ) ;
299299
300300 const allErrors : SettingResolutionError [ ] = [ ] ;
301+ let workspaceFolderResolved = false ;
301302
302303 for ( const folder of folders ) {
303304 try {
@@ -328,6 +329,10 @@ export async function applyInitialEnvironmentSelection(
328329 // Cache only — NO settings.json write (shouldPersistSettings = false)
329330 await envManagers . setEnvironment ( folder . uri , env , false ) ;
330331
332+ if ( env ) {
333+ workspaceFolderResolved = true ;
334+ }
335+
331336 traceInfo (
332337 `[interpreterSelection] ${ folder . name } : ${ env ?. displayName ?? 'none' } (source: ${ result . source } )` ,
333338 ) ;
@@ -336,35 +341,72 @@ export async function applyInitialEnvironmentSelection(
336341 }
337342 }
338343
339- // Also apply initial selection for global scope (no workspace folder)
340- // This ensures defaultInterpreterPath is respected even without a workspace
341- try {
342- const globalStopWatch = new StopWatch ( ) ;
343- const { result, errors } = await resolvePriorityChainCore ( undefined , envManagers , undefined , nativeFinder , api ) ;
344- allErrors . push ( ...errors ) ;
344+ // Global scope: resolve a fallback Python environment for files opened OUTSIDE all
345+ // workspace folders (e.g., /tmp/script.py). This is NOT a workspace folder — every
346+ // workspace folder was already fully resolved and cached in the for-loop above,
347+ // so switching between workspace folders is unaffected by whether this runs now or later.
348+ //
349+ // When at least one workspace folder resolved, we defer global scope to the background
350+ // so it doesn't block post-selection startup (clearHangWatchdog, terminal init, telemetry).
351+ // Errors inside resolveGlobalScope are handled internally:
352+ // - Setting resolution errors (bad paths, unknown managers) → notifyUserOfSettingErrors
353+ // - Unexpected crashes → logged via traceError, never silently swallowed
354+ const resolveGlobalScope = async ( ) => {
355+ try {
356+ const globalStopWatch = new StopWatch ( ) ;
357+ const { result, errors : globalErrors } = await resolvePriorityChainCore (
358+ undefined ,
359+ envManagers ,
360+ undefined ,
361+ nativeFinder ,
362+ api ,
363+ ) ;
345364
346- const isPathA = result . environment !== undefined ;
365+ const isPathA = result . environment !== undefined ;
366+ const env = result . environment ?? ( await result . manager . get ( undefined ) ) ;
347367
348- // Get the specific environment if not already resolved
349- const env = result . environment ?? ( await result . manager . get ( undefined ) ) ;
368+ sendTelemetryEvent ( EventNames . ENV_SELECTION_RESULT , globalStopWatch . elapsedTime , {
369+ scope : 'global' ,
370+ prioritySource : result . source ,
371+ managerId : result . manager . id ,
372+ resolutionPath : isPathA ? 'envPreResolved' : 'managerDiscovery' ,
373+ hasPersistedSelection : env !== undefined ,
374+ } ) ;
350375
351- sendTelemetryEvent ( EventNames . ENV_SELECTION_RESULT , globalStopWatch . elapsedTime , {
352- scope : 'global' ,
353- prioritySource : result . source ,
354- managerId : result . manager . id ,
355- resolutionPath : isPathA ? 'envPreResolved' : 'managerDiscovery' ,
356- hasPersistedSelection : env !== undefined ,
357- } ) ;
376+ await envManagers . setEnvironments ( 'global' , env , false ) ;
358377
359- // Cache only — NO settings.json write (shouldPersistSettings = false)
360- await envManagers . setEnvironments ( 'global' , env , false ) ;
378+ traceInfo ( `[interpreterSelection] global: ${ env ?. displayName ?? 'none' } (source: ${ result . source } )` ) ;
361379
362- traceInfo ( `[interpreterSelection] global: ${ env ?. displayName ?? 'none' } (source: ${ result . source } )` ) ;
363- } catch ( err ) {
364- traceError ( `[interpreterSelection] Failed to set global environment: ${ err } ` ) ;
380+ if ( globalErrors . length > 0 ) {
381+ await notifyUserOfSettingErrors ( globalErrors ) ;
382+ }
383+ } catch ( err ) {
384+ traceError ( `[interpreterSelection] Failed to set global environment: ${ err } ` ) ;
385+ }
386+ } ;
387+
388+ if ( workspaceFolderResolved ) {
389+ // At least one workspace folder got a non-undefined environment (in multi-root,
390+ // ANY folder succeeding is sufficient). ALL workspace folder envs are already
391+ // active via setEnvironment calls in the loop — switching between folders in a
392+ // multi-root workspace is not affected by deferring the global scope.
393+ // Defer global scope to a background task so we don't block post-selection
394+ // startup work in extension.ts (clearHangWatchdog, terminal init, telemetry).
395+ // The outer .catch is a safety net — resolveGlobalScope has its own try/catch,
396+ // so this only fires if the inner handler itself throws unexpectedly.
397+ traceInfo ( '[interpreterSelection] Workspace env resolved, deferring global scope to background' ) ;
398+ resolveGlobalScope ( ) . catch ( ( err ) =>
399+ traceError ( `[interpreterSelection] Background global scope resolution failed: ${ err } ` ) ,
400+ ) ;
401+ } else {
402+ // Either: (a) no workspace folders are open, (b) every folder resolved with
403+ // env=undefined (no Python found), or (c) every folder threw an error.
404+ // In all cases the global environment is the user's primary fallback,
405+ // so we must await it before returning.
406+ await resolveGlobalScope ( ) ;
365407 }
366408
367- // Notify user if any settings could not be applied
409+ // Notify user if any workspace-scoped settings could not be applied
368410 if ( allErrors . length > 0 ) {
369411 await notifyUserOfSettingErrors ( allErrors ) ;
370412 }
0 commit comments