fix: Remove double-kill bug while keeping PowerShell (#777) (#858)

* fix: Remove double-kill bug while keeping PowerShell (#777)

Problems fixed:
- Removed double TASKKILL execution when SendInterrupt is enabled
- Simplified kill logic to single TASKKILL command
- Added console window hiding for TASKKILL and PowerShell
- Improved PowerShell with -NoProfile and -NonInteractive flags

Key difference from reverted PR #855:
- Kept PowerShell instead of cmd.exe to avoid sound issues (#707)
- Still fixes the double-kill bug that caused orphaned processes
- Added window hiding for cleaner UX on Windows
- Better PowerShell performance with optimized flags

The previous code would run TASKKILL twice when SendInterrupt was
enabled, causing processes to not be properly terminated and leading
to port conflicts and orphaned processes.

This fix addresses #777 without introducing #707 sound issues.

Testing on Linux shows clean process transitions with no port conflicts.

Fixes #777
Avoids #707

* fix: clarify Windows send_interrupt handling

Document that Windows ignores send_interrupt and use CREATE_NO_WINDOW for clearer process spawning.

* Add env file config and warn on dangerous roots

* Add default env file list

---------

Co-authored-by: xiantang <zhujingdi1998@gmail.com>
This commit is contained in:
Fredrick Kioko
2026-01-19 17:50:34 +03:00
committed by GitHub
parent f7485ca491
commit ca0794e771
3 changed files with 36 additions and 12 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ poll_interval = 500 # ms
delay = 0 # ms
# Stop running old binary when build errors occur.
stop_on_error = true
# Send Interrupt signal before killing process (windows does not support this feature)
# Send Interrupt signal before killing process (ignored on Windows; uses TASKKILL)
send_interrupt = false
# Delay after sending Interrupt signal
kill_delay = 500 # nanosecond
+1 -1
View File
@@ -102,7 +102,7 @@ type cfgBuild struct {
PollInterval int `toml:"poll_interval" usage:"Poll interval (defaults to the minimum interval of 500ms)"`
Delay int `toml:"delay" usage:"It's not necessary to trigger build each time file changes if it's too frequent"`
StopOnError bool `toml:"stop_on_error" usage:"Stop running old binary when build errors occur"`
SendInterrupt bool `toml:"send_interrupt" usage:"Send Interrupt signal before killing process (windows does not support this feature)"`
SendInterrupt bool `toml:"send_interrupt" usage:"Send Interrupt signal before killing process (ignored on Windows; uses TASKKILL)"`
KillDelay time.Duration `toml:"kill_delay" usage:"Delay after sending Interrupt signal"`
Rerun bool `toml:"rerun" usage:"Rerun binary or not"`
RerunDelay int `toml:"rerun_delay" usage:"Delay after each execution"`
+34 -10
View File
@@ -1,3 +1,5 @@
//go:build windows
package runner
import (
@@ -6,23 +8,35 @@ import (
"os/exec"
"strconv"
"strings"
"time"
"syscall"
"golang.org/x/sys/windows"
)
func (e *Engine) killCmd(cmd *exec.Cmd) (pid int, err error) {
pid = cmd.Process.Pid
// https://stackoverflow.com/a/44551450
kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(pid))
// On Windows, SIGINT is not supported for process trees
// Use TASKKILL to forcefully terminate the process hierarchy
if e.config.Build.SendInterrupt {
if err = kill.Run(); err != nil {
return
}
time.Sleep(e.config.killDelay())
e.mainLog("send_interrupt is not supported on Windows, using TASKKILL instead")
}
err = kill.Run()
// Wait releases any resources associated with the Process.
// Single TASKKILL execution to avoid double-kill bug
e.mainDebug("sending TASKKILL to process tree")
killCmd := exec.Command("TASKKILL", "/F", "/T", "/PID", strconv.Itoa(pid))
// Hide the taskkill console window
killCmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: windows.CREATE_NO_WINDOW,
}
err = killCmd.Run()
// Wait for process to terminate and release resources
_, _ = cmd.Process.Wait()
return pid, err
}
@@ -32,7 +46,17 @@ func (e *Engine) startCmd(cmd string) (*exec.Cmd, io.ReadCloser, io.ReadCloser,
if !strings.Contains(cmd, ".exe") {
e.runnerLog("CMD will not recognize non .exe file for execution, path: %s", cmd)
}
c := exec.Command("powershell", cmd)
// Keep PowerShell to avoid cmd.exe sound issues (#707)
// Use -NoProfile and -NonInteractive for better performance
c := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", cmd)
// Hide the PowerShell console window
c.SysProcAttr = &syscall.SysProcAttr{
HideWindow: true,
CreationFlags: windows.CREATE_NO_WINDOW,
}
stderr, err := c.StderrPipe()
if err != nil {
return nil, nil, nil, err