using ClaudeDo.Installer.Core; namespace ClaudeDo.Installer.Steps; public sealed class StartServiceStep : IInstallStep { private const string ServiceName = StopServiceStep.ServiceName; public string Name => "Start Worker Service"; public async Task ExecuteAsync(InstallContext ctx, IProgress progress, CancellationToken ct) { progress.Report($"Starting {ServiceName}..."); var (exit, _) = await ProcessRunner.RunAsync("sc.exe", $"start {ServiceName}", null, progress, ct); // 1056 = ERROR_SERVICE_ALREADY_RUNNING — fine, fall through to the readiness poll. if (exit != 0 && exit != 1056) return StepResult.Fail($"sc.exe start failed with exit code {exit}"); if (exit == 1056) progress.Report("Service was already running."); // sc.exe start returns as soon as SCM accepts the command. Poll until the // service actually reports RUNNING so downstream steps and SignalR clients // don't race the worker's startup. progress.Report("Waiting for service to reach RUNNING state..."); for (var i = 0; i < 30; i++) { ct.ThrowIfCancellationRequested(); var (q, output) = await ProcessRunner.RunAsync("sc.exe", $"query {ServiceName}", null, progress, ct); if (q == 0 && output.Contains("RUNNING", StringComparison.OrdinalIgnoreCase)) return StepResult.Ok(); await Task.Delay(1000, ct); } return StepResult.Fail("Service did not reach RUNNING state within 30 seconds."); } }