From 4148dcdb18051f18ece8805874c46835b5204d62 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Mon, 1 Jun 2026 13:26:47 +0200 Subject: [PATCH] fix(installer): stop the running app before updating, not just the worker A running ClaudeDo.App.exe locks the install\app directory, so the extract step's Directory.Move failed with "Access to the path '...\app' is denied" during an update. StopWorkerStep now also terminates app processes scoped to the install dir (benefits uninstall too). Co-Authored-By: Claude Opus 4.7 --- .../Steps/StopWorkerStep.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) 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();