You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Cancelling NativeCommandExecutor.Execute kills only the shell: work in a subshell, pipeline or child process keeps running and completes its side effects #97
#53 says "the sync path already kills the process, so only the async path is affected" — true only when the shell execs the command directly. Its suggested TryKill fallback would carry this gap into the async fix.
Reproduction (verified on Linux, net10.0, built from 415fb1b)
ICommandExecutorex=newNativeCommandExecutor();usingvarcts=newCancellationTokenSource(500);varsw=Stopwatch.StartNew();varr=ex.Execute($"(sleep 3; touch {marker}); echo done",null,null,cts.Token);Console.WriteLine($"sync cancel returned after {sw.ElapsedMilliseconds}ms: exit={r.ExitCode} err=[{r.StandardError}]");Thread.Sleep(4000);Console.WriteLine($"marker exists after cancel? {File.Exists(marker)}");
Observed:
sync cancel returned after 2513ms: exit=-1 err=[Operation was cancelled.]
marker exists after cancel? True
Expected: return within about one poll interval of the cancel, and the marker is never created.
CommandExecutor_Sync_Execute_Cancels_A_Running_Process (CommandExecutorTests.cs L224) misses this: it only asserts elapsed time, and on Unix a bare sleep N is exec'd by the shell, so the one process killed is the right one.
Why it matters
The caller is told the command was cancelled while the work continues and performs its side effects (writes, builds, deploys). On Windows this is the normal case.
Suggested fix
Kill the whole tree: process.Kill(entireProcessTree: true) on every target except netstandard2.1 (available from netcoreapp3.0), with Kill() as the netstandard2.1 fallback behind the narrowest #if.
A test cancels Execute on a command whose work runs in a grandchild of the shell (Unix: (sleep N; touch marker); true; Windows: ping/timeout then a marker write), asserting the marker is never created and the call returns well under the drain-abandon timeout.
Priority: High. The caller is told the command was cancelled while it keeps running and completes its side effects. On Windows, where cmd.exe /c always forks, this is the normal case. Every cancel also returns about 2 s late and leaves drain threads blocked. The fix is small: Kill(entireProcessTree: true), with a netstandard2.1 fallback.
Area / suggested assignment: Command executors: Essentials.CommandExecutors.Native/NativeCommandExecutor.cs (TryKill), plus a stronger test than the timing-only CommandExecutor_Sync_Execute_Cancels_A_Running_Process.
Activity
matt-edmondson commentedon Oct 7, 2026
Triage
cmd.exe /calways forks, this is the normal case. Every cancel also returns about 2 s late and leaves drain threads blocked. The fix is small:Kill(entireProcessTree: true), with a netstandard2.1 fallback.Essentials.CommandExecutors.Native/NativeCommandExecutor.cs(TryKill), plus a stronger test than the timing-onlyCommandExecutor_Sync_Execute_Cancels_A_Running_Process.Generated by Claude Code