fix ci on windows: split detach helper into posix/windows variants
syscall.SysProcAttr.Setsid is posix-only — unknown field on windows. Extracted the detach-on-start logic into a detachChild function variable, implemented in botcmd_unix.go (Setsid) and botcmd_windows.go (DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP creation flags).
- commit
- 682c64f494f3ae272adb98a7557f1b61e39901c9
- author
- patriceckhart · · 2026-04-18 08:58 UTC
- parent
- 2158c27
- signature
- unsigned
3 files · +49 -2
M internal/agent/botcmd.go +11 -2
| @@ -19,6 +19,13 @@ import ( | |||
| 19 | 19 | "github.com/patriceckhart/zot/internal/core" | |
| 20 | 20 | ) | |
| 21 | 21 | ||
| 22 | + | // detachChild configures cmd to run in its own process group so tty | |
| 23 | + | // signals sent to the parent (SIGINT, SIGHUP on logout) don't also | |
| 24 | + | // reach the detached bot. Platform-specific: setsid on unix, a noop | |
| 25 | + | // on windows (Go's spawn path already detaches when no console is | |
| 26 | + | // inherited). See botcmd_unix.go and botcmd_windows.go. | |
| 27 | + | var detachChild func(cmd *exec.Cmd) | |
| 28 | + | ||
| 22 | 29 | // runBotCommand dispatches `zot telegram-bot ...` subcommands. The | |
| 23 | 30 | // short alias "tg" is also accepted. Returns true if rawArgs begins | |
| 24 | 31 | // with a recognised subcommand, false otherwise. | |
| @@ -230,8 +237,10 @@ func botStart(rawTail []string) error { | |||
| 230 | 237 | cmd.Stdout = logFile | |
| 231 | 238 | cmd.Stderr = logFile | |
| 232 | 239 | cmd.Stdin = nil | |
| 233 | - | // Detach: new session so terminal signals don't reach the child. | |
| 234 | - | cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} | |
| 240 | + | // Detach: new session / new process group so terminal signals | |
| 241 | + | // don't reach the child. Impl lives in botcmd_unix.go / | |
| 242 | + | // botcmd_windows.go because Setsid is posix-only. | |
| 243 | + | detachChild(cmd) | |
| 235 | 244 | ||
| 236 | 245 | if err := cmd.Start(); err != nil { | |
| 237 | 246 | return fmt.Errorf("spawn: %w", err) | |
| @@ -19,6 +19,13 @@ import ( | |||||
| 19 | "github.com/patriceckhart/zot/internal/core" | 19 | "github.com/patriceckhart/zot/internal/core" | ||
| 20 | ) | 20 | ) | ||
| 21 | 21 | ||||
| 22 | + | // detachChild configures cmd to run in its own process group so tty | |||
| 23 | + | // signals sent to the parent (SIGINT, SIGHUP on logout) don't also | |||
| 24 | + | // reach the detached bot. Platform-specific: setsid on unix, a noop | |||
| 25 | + | // on windows (Go's spawn path already detaches when no console is | |||
| 26 | + | // inherited). See botcmd_unix.go and botcmd_windows.go. | |||
| 27 | + | var detachChild func(cmd *exec.Cmd) | |||
| 28 | + | ||||
| 22 | // runBotCommand dispatches `zot telegram-bot ...` subcommands. The | 29 | // runBotCommand dispatches `zot telegram-bot ...` subcommands. The | ||
| 23 | // short alias "tg" is also accepted. Returns true if rawArgs begins | 30 | // short alias "tg" is also accepted. Returns true if rawArgs begins | ||
| 24 | // with a recognised subcommand, false otherwise. | 31 | // with a recognised subcommand, false otherwise. | ||
| @@ -230,8 +237,10 @@ func botStart(rawTail []string) error { | |||||
| 230 | cmd.Stdout = logFile | 237 | cmd.Stdout = logFile | ||
| 231 | cmd.Stderr = logFile | 238 | cmd.Stderr = logFile | ||
| 232 | cmd.Stdin = nil | 239 | cmd.Stdin = nil | ||
| 233 | - | // Detach: new session so terminal signals don't reach the child. | 240 | + | // Detach: new session / new process group so terminal signals |
| 234 | - | cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} | 241 | + | // don't reach the child. Impl lives in botcmd_unix.go / |
| 242 | + | // botcmd_windows.go because Setsid is posix-only. | |||
| 243 | + | detachChild(cmd) | |||
| 235 | 244 | ||||
| 236 | if err := cmd.Start(); err != nil { | 245 | if err := cmd.Start(); err != nil { | ||
| 237 | return fmt.Errorf("spawn: %w", err) | 246 | return fmt.Errorf("spawn: %w", err) | ||
A internal/agent/botcmd_unix.go +17 -0
| @@ -0,0 +1,17 @@ | |||
| 1 | + | //go:build !windows | |
| 2 | + | ||
| 3 | + | package agent | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "os/exec" | |
| 7 | + | "syscall" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | // detachChild runs the bot in a new session so tty signals sent to | |
| 11 | + | // the parent shell (SIGINT when the terminal closes, SIGHUP on | |
| 12 | + | // logout) don't propagate to the detached bot. | |
| 13 | + | func init() { | |
| 14 | + | detachChild = func(cmd *exec.Cmd) { | |
| 15 | + | cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} | |
| 16 | + | } | |
| 17 | + | } | |
| @@ -0,0 +1,17 @@ | |||||
| 1 | + | //go:build !windows | |||
| 2 | + | ||||
| 3 | + | package agent | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "os/exec" | |||
| 7 | + | "syscall" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | // detachChild runs the bot in a new session so tty signals sent to | |||
| 11 | + | // the parent shell (SIGINT when the terminal closes, SIGHUP on | |||
| 12 | + | // logout) don't propagate to the detached bot. | |||
| 13 | + | func init() { | |||
| 14 | + | detachChild = func(cmd *exec.Cmd) { | |||
| 15 | + | cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} | |||
| 16 | + | } | |||
| 17 | + | } | |||
A internal/agent/botcmd_windows.go +21 -0
| @@ -0,0 +1,21 @@ | |||
| 1 | + | //go:build windows | |
| 2 | + | ||
| 3 | + | package agent | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "os/exec" | |
| 7 | + | "syscall" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | // detachChild spawns the bot in its own process group via the | |
| 11 | + | // DETACHED_PROCESS + CREATE_NEW_PROCESS_GROUP creation flags. Windows | |
| 12 | + | // has no direct setsid equivalent; these two flags combined give the | |
| 13 | + | // child a clean console-less group that ctrl+c on the parent won't | |
| 14 | + | // reach. | |
| 15 | + | func init() { | |
| 16 | + | detachChild = func(cmd *exec.Cmd) { | |
| 17 | + | cmd.SysProcAttr = &syscall.SysProcAttr{ | |
| 18 | + | CreationFlags: 0x00000008 | 0x00000200, // DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | |
| 19 | + | } | |
| 20 | + | } | |
| 21 | + | } | |
| @@ -0,0 +1,21 @@ | |||||
| 1 | + | //go:build windows | |||
| 2 | + | ||||
| 3 | + | package agent | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "os/exec" | |||
| 7 | + | "syscall" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | // detachChild spawns the bot in its own process group via the | |||
| 11 | + | // DETACHED_PROCESS + CREATE_NEW_PROCESS_GROUP creation flags. Windows | |||
| 12 | + | // has no direct setsid equivalent; these two flags combined give the | |||
| 13 | + | // child a clean console-less group that ctrl+c on the parent won't | |||
| 14 | + | // reach. | |||
| 15 | + | func init() { | |||
| 16 | + | detachChild = func(cmd *exec.Cmd) { | |||
| 17 | + | cmd.SysProcAttr = &syscall.SysProcAttr{ | |||
| 18 | + | CreationFlags: 0x00000008 | 0x00000200, // DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | |||
| 19 | + | } | |||
| 20 | + | } | |||
| 21 | + | } | |||