All checks were successful
Release / release (push) Successful in 34s
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 <noreply@anthropic.com>
53 lines
1.9 KiB
C#
53 lines
1.9 KiB
C#
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<StepResult> ExecuteAsync(InstallContext ctx, IProgress<string> 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; }
|
|
}
|
|
}
|