diff --git a/src/ClaudeDo.Installer/Steps/StopWorkerStep.cs b/src/ClaudeDo.Installer/Steps/StopWorkerStep.cs index 753249a..e004153 100644 --- a/src/ClaudeDo.Installer/Steps/StopWorkerStep.cs +++ b/src/ClaudeDo.Installer/Steps/StopWorkerStep.cs @@ -7,25 +7,32 @@ namespace ClaudeDo.Installer.Steps; public sealed class StopWorkerStep : IInstallStep { public const string LegacyTaskName = "ClaudeDoWorker"; - public const string ProcessName = "ClaudeDo.Worker"; + + // Both must be stopped before the install dir is touched: a running app/worker + // exe locks its directory, so Directory.Move during extraction would otherwise + // fail with "Access to the path '...\app' is denied". + private static readonly string[] ProcessNames = { "ClaudeDo.Worker", "ClaudeDo.App" }; public string Name => "Stop Worker"; public async Task ExecuteAsync(InstallContext ctx, IProgress progress, CancellationToken ct) { - progress.Report("Stopping worker process (if running)..."); + progress.Report("Stopping ClaudeDo processes (if running)..."); var installDir = ctx.InstallDirectory; - foreach (var p in Process.GetProcessesByName(ProcessName)) + foreach (var name in ProcessNames) { - try + foreach (var p in Process.GetProcessesByName(name)) { - var path = p.MainModule?.FileName; - if (path is not null && !IsUnder(path, installDir)) continue; - p.Kill(entireProcessTree: true); - p.WaitForExit(10000); + try + { + var path = p.MainModule?.FileName; + if (path is not null && !IsUnder(path, installDir)) continue; + p.Kill(entireProcessTree: true); + p.WaitForExit(10000); + } + catch { /* process may have exited or be inaccessible */ } + finally { p.Dispose(); } } - catch { /* process may have exited or be inaccessible */ } - finally { p.Dispose(); } } await Task.CompletedTask; return StepResult.Ok();