From f5dfa60eab7ae57ded33c3f5a5ddac5e8b059f75 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:53:42 +0000 Subject: [PATCH 1/2] Initial plan From 73d66737f1e62230d26b4df8936c636d348bc9f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:57:30 +0000 Subject: [PATCH 2/2] fix: prevent SIGINT from propagating to dind cleanup subprocesses on Ctrl+C When Ctrl+C is pressed the terminal SIGINT is broadcast to the entire foreground process group. The docker stop/rm/network-rm subprocesses spawned by Stop() were in that group and exited with code 130 before completing, leaving the dind container and network running. - Add setSysProcAttr() (platform-specific) to place each cleanup subprocess in its own process group (Setpgid: true on Unix), shielding it from the terminal's SIGINT. - Replace docker stop + docker rm with docker rm -f, which force-kills and removes the container atomically, eliminating the "container is running" error even if the stop step were somehow interrupted. Co-authored-by: mtsfoni <80639729+mtsfoni@users.noreply.github.com> --- internal/dind/dind.go | 11 ++++++----- internal/dind/sysattr_unix.go | 14 ++++++++++++++ internal/dind/sysattr_windows.go | 9 +++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) create mode 100644 internal/dind/sysattr_unix.go create mode 100644 internal/dind/sysattr_windows.go diff --git a/internal/dind/dind.go b/internal/dind/dind.go index dcfc455..5ef4816 100644 --- a/internal/dind/dind.go +++ b/internal/dind/dind.go @@ -75,10 +75,7 @@ func Start(sessionID string) (*Instance, error) { // Stop removes the dind container and the shared network. Errors are printed to // stderr but not returned so that deferred calls always complete. func (i *Instance) Stop() { - if out, err := run("docker", "stop", i.ContainerName); err != nil { - fmt.Fprintf(os.Stderr, "construct: stop dind: %v\n%s\n", err, out) - } - if out, err := run("docker", "rm", i.ContainerName); err != nil { + if out, err := run("docker", "rm", "-f", i.ContainerName); err != nil { fmt.Fprintf(os.Stderr, "construct: rm dind: %v\n%s\n", err, out) } if out, err := run("docker", "network", "rm", i.NetworkName); err != nil { @@ -103,7 +100,11 @@ func (i *Instance) waitReady() error { } // run executes a command and returns its combined output and any error. +// It runs in its own process group so that SIGINT from Ctrl+C is not +// forwarded to the subprocess. func run(name string, args ...string) (string, error) { - out, err := exec.Command(name, args...).CombinedOutput() + cmd := exec.Command(name, args...) + setSysProcAttr(cmd) + out, err := cmd.CombinedOutput() return string(out), err } diff --git a/internal/dind/sysattr_unix.go b/internal/dind/sysattr_unix.go new file mode 100644 index 0000000..415e622 --- /dev/null +++ b/internal/dind/sysattr_unix.go @@ -0,0 +1,14 @@ +//go:build !windows + +package dind + +import ( + "os/exec" + "syscall" +) + +// setSysProcAttr places the command in its own process group so that a +// SIGINT from Ctrl+C on the terminal is not forwarded to the subprocess. +func setSysProcAttr(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} +} diff --git a/internal/dind/sysattr_windows.go b/internal/dind/sysattr_windows.go new file mode 100644 index 0000000..55ef744 --- /dev/null +++ b/internal/dind/sysattr_windows.go @@ -0,0 +1,9 @@ +//go:build windows + +package dind + +import "os/exec" + +// setSysProcAttr is a no-op on Windows; process group isolation is not +// needed because Windows does not use POSIX process groups for signal delivery. +func setSysProcAttr(cmd *exec.Cmd) {}