@@ -383,11 +383,13 @@ internal static void ConfigurePageViewModels(IServiceCollection services)
383383 provider . GetRequiredService < IModelIndexService > ( ) ,
384384 provider . GetRequiredService < Lazy < IModelDownloadLinkHandler > > ( ) ,
385385 provider . GetRequiredService < INotificationService > ( ) ,
386+ provider . GetRequiredService < IAppNotificationService > ( ) ,
386387 provider . GetRequiredService < IAnalyticsHelper > ( ) ,
387388 provider . GetRequiredService < IUpdateHelper > ( ) ,
388389 provider . GetRequiredService < ISecretsManager > ( ) ,
389390 provider . GetRequiredService < INavigationService < MainWindowViewModel > > ( ) ,
390- provider . GetRequiredService < INavigationService < SettingsViewModel > > ( )
391+ provider . GetRequiredService < INavigationService < SettingsViewModel > > ( ) ,
392+ provider . GetRequiredService < IDistributedSubscriber < string , Uri > > ( )
391393 )
392394 {
393395 Pages =
@@ -459,6 +461,14 @@ internal static IServiceCollection ConfigureServices(bool disableMessagePipeInte
459461 // Named pipe interprocess communication on Windows and Linux for uri handling
460462 if ( ! disableMessagePipeInterprocess && ( Compat . IsWindows || Compat . IsLinux ) )
461463 {
464+ // On Linux, named pipes use Unix domain sockets which leave stale socket files
465+ // if the app crashes or exits without cleanup. Delete the stale file if no other
466+ // instance is running, so the new instance can bind successfully.
467+ if ( Compat . IsLinux )
468+ {
469+ CleanupStaleUnixSocket ( "StabilityMatrix" ) ;
470+ }
471+
462472 services . AddMessagePipe ( ) . AddNamedPipeInterprocess ( "StabilityMatrix" ) ;
463473 }
464474 else
@@ -1005,6 +1015,47 @@ private void OnExit(object? sender, EventArgs _)
10051015 }
10061016 }
10071017
1018+ /// <summary>
1019+ /// On Linux, .NET named pipes use Unix domain sockets. If the app exits without cleanup,
1020+ /// the socket file remains and prevents the next instance from binding.
1021+ /// This deletes the stale socket file if no other instance holds it.
1022+ /// </summary>
1023+ private static void CleanupStaleUnixSocket ( string pipeName )
1024+ {
1025+ try
1026+ {
1027+ var tempPath = Path . GetTempPath ( ) ;
1028+ var socketPath = Path . Combine ( tempPath , $ "CoreFxPipe_{ pipeName } ") ;
1029+
1030+ if ( ! File . Exists ( socketPath ) )
1031+ return ;
1032+
1033+ // Try connecting to see if another instance is actually listening
1034+ using var socket = new System . Net . Sockets . Socket (
1035+ System . Net . Sockets . AddressFamily . Unix ,
1036+ System . Net . Sockets . SocketType . Stream ,
1037+ System . Net . Sockets . ProtocolType . Unspecified
1038+ ) ;
1039+
1040+ try
1041+ {
1042+ socket . Connect ( new System . Net . Sockets . UnixDomainSocketEndPoint ( socketPath ) ) ;
1043+ // Connected successfully - another instance is running, don't delete
1044+ }
1045+ catch ( System . Net . Sockets . SocketException ex )
1046+ when ( ex . SocketErrorCode == System . Net . Sockets . SocketError . ConnectionRefused )
1047+ {
1048+ // A refused connection means the socket file exists but nothing is listening anymore.
1049+ File . Delete ( socketPath ) ;
1050+ Logger . Info ( "Deleted stale Unix domain socket: {SocketPath}" , socketPath ) ;
1051+ }
1052+ }
1053+ catch ( Exception e )
1054+ {
1055+ Logger . Warn ( e , "Failed to clean up stale Unix domain socket" ) ;
1056+ }
1057+ }
1058+
10081059 private static void OnServiceProviderDisposing ( ServiceProvider serviceProvider )
10091060 {
10101061 // Force materialize SharedFolders so its DisposeAsync is called
0 commit comments