Fix proxy-exec and step-runner hangs when scripts spawn background processes
What
Fixes a class of hang where commands spawned by proxy-exec, step-runner's
script execution, and script_legacy's executor would block indefinitely if
the script started a background process ((daemon &), nohup foo &, etc.).
Root cause
When cmd.Stdout / cmd.Stderr are non-*os.File writers, os/exec creates
internal pipes and copy goroutines. cmd.Run() / cmd.Wait() waits for those
goroutines to see EOF. A backgrounded grandchild inherits the pipe write-end on
fork, keeps it open after the direct child exits, EOF never arrives, and the
job hangs until the grandchild eventually exits (or forever).
Changes
commands/helpers/proxy_exec.go— setcmd.WaitDelay. proxy-exec stays semantically transparent (matchessh -cbehaviour for orphaned processes);WaitDelayjust bounds the pipe drain after the direct child exits.commands/steps/steps.goandfunctions/script_legacy/internal/executor.go— switch togracefulexitcmd.New(...). Step boundaries are a meaningful cleanup point, so we now run the script in its own process group, send SIGTERM on context cancel, and SIGKILL the group afterWait()to reap any survivors. On Windows,gracefulexitcmduses Job Objects for the equivalent guarantee.
Behaviour notes
- proxy-exec: orphaned processes survive (as they would under
sh -c); their stdout/stderr stops being captured the moment the direct child exits. - step-runner / script_legacy: orphaned processes are now killed when the step
exits. CI scripts relying on cross-step process survival via
(cmd &)would need to use a different mechanism (services, sidecar containers, etc.).
Tests
- New
TestProxyExecBackgroundProcessregression test reproduces the hang against unmodified code (fails at 5s deadline) and passes in ~1s with the fix. Skipped on Windows (uses POSIX double-fork semantics). - Existing
TestServe/with_context_being_canceled_from_the_outsideupdated fromsignal: killedtosignal: terminatedto reflect the new graceful SIGTERM-first behaviour. - All other existing tests in the affected packages pass unchanged.