@@ -339,16 +339,27 @@ public int DisplayIncompatibleVersionError(AppHostIncompatibleException ex, stri
339339
340340 public void DisplayError ( string errorMessage )
341341 {
342- var result = _extensionTaskChannel . Writer . TryWrite ( ( ) => Backchannel . DisplayErrorAsync ( errorMessage . RemoveSpectreFormatting ( ) , _cancellationToken ) ) ;
342+ // Serialize the local console write onto the same channel as the backchannel call so
343+ // it stays ordered with prior queued operations (e.g. DisplayLines). Otherwise the
344+ // synchronous Spectre write would land in the IDE debug console (via stdout/stderr
345+ // capture) before earlier asynchronous DisplayLines RPCs had flushed, producing
346+ // out-of-order output like an error message preceding the lines that explain it.
347+ var result = _extensionTaskChannel . Writer . TryWrite ( async ( ) =>
348+ {
349+ await Backchannel . DisplayErrorAsync ( errorMessage . RemoveSpectreFormatting ( ) , _cancellationToken ) ;
350+ _consoleInteractionService . DisplayError ( errorMessage ) ;
351+ } ) ;
343352 Debug . Assert ( result ) ;
344- _consoleInteractionService . DisplayError ( errorMessage ) ;
345353 }
346354
347355 public void DisplayMessage ( KnownEmoji emoji , string message , bool allowMarkup = false )
348356 {
349- var result = _extensionTaskChannel . Writer . TryWrite ( ( ) => Backchannel . DisplayMessageAsync ( emoji . Name , message . RemoveSpectreFormatting ( ) , _cancellationToken ) ) ;
357+ var result = _extensionTaskChannel . Writer . TryWrite ( async ( ) =>
358+ {
359+ await Backchannel . DisplayMessageAsync ( emoji . Name , message . RemoveSpectreFormatting ( ) , _cancellationToken ) ;
360+ _consoleInteractionService . DisplayMessage ( emoji , message , allowMarkup ) ;
361+ } ) ;
350362 Debug . Assert ( result ) ;
351- _consoleInteractionService . DisplayMessage ( emoji , message , allowMarkup ) ;
352363 }
353364
354365 public void DisplaySuccess ( string message , bool allowMarkup = false )
@@ -378,11 +389,20 @@ public void DisplayDashboardUrls(DashboardUrlsState dashboardUrls)
378389
379390 public void DisplayLines ( IEnumerable < ( OutputLineStream Stream , string Line ) > lines )
380391 {
381- var result = _extensionTaskChannel . Writer . TryWrite ( ( ) => Backchannel . DisplayLinesAsync ( lines . Select ( line => new DisplayLineState (
392+ // Materialize so we can iterate twice without re-enumerating a possibly lazy/one-shot source.
393+ var materialized = lines as IReadOnlyCollection < ( OutputLineStream Stream , string Line ) > ?? lines . ToList ( ) ;
394+
395+ var result = _extensionTaskChannel . Writer . TryWrite ( ( ) => Backchannel . DisplayLinesAsync ( materialized . Select ( line => new DisplayLineState (
382396 line . Stream == OutputLineStream . StdOut ? "stdout" : "stderr" ,
383397 line . Line . RemoveSpectreFormatting ( ) ) ) , _cancellationToken ) ) ;
384398 Debug . Assert ( result ) ;
385- _consoleInteractionService . DisplayLines ( lines ) ;
399+
400+ // Intentionally do NOT also write to the local console here. Unlike most Display* methods
401+ // (whose backchannel sinks are distinct from the debug console — popups, status bar, log
402+ // channel, etc.), the extension's `displayLines` RPC routes the lines into the active
403+ // AppHost debug console. The CLI's stdout/stderr is also captured by the extension and
404+ // forwarded into that same debug console, so calling _consoleInteractionService.DisplayLines
405+ // here would surface every line twice.
386406 }
387407
388408 public void DisplayCancellationMessage ( )
0 commit comments