using System.Diagnostics; using System.IO; using ClaudeDo.Installer.Core; namespace ClaudeDo.Installer.Steps; public sealed class StopWorkerStep : IInstallStep { public const string LegacyTaskName = "ClaudeDoWorker"; // 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 ClaudeDo processes (if running)..."); var installDir = ctx.InstallDirectory; foreach (var name in ProcessNames) { foreach (var p in Process.GetProcessesByName(name)) { 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(); } } } await Task.CompletedTask; return StepResult.Ok(); } private static bool IsUnder(string filePath, string dir) { try { if (string.IsNullOrWhiteSpace(dir)) return true; // can't scope — be permissive var full = Path.GetFullPath(filePath); var root = Path.GetFullPath(dir).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; return full.StartsWith(root, StringComparison.OrdinalIgnoreCase); } catch { return false; } } }