Documentation
¶
Overview ¶
Package execx provides explicit helpers for spawning subprocesses with a chosen TTY attachment mode, replacing env-var signalling with real OS state.
Use NonInteractive when the subprocess must not prompt (tests, automation, hooks that shouldn't block). Use Interactive when the subprocess should inherit the parent's controlling TTY (the default for exec.Command).
Index ¶
Constants ¶
const KillWaitDelay = 10 * time.Second
KillWaitDelay bounds how long Wait blocks after ctx-cancel before exec force-closes the subprocess I/O pipes. Without it, a descendant that inherited the output pipe (a sandbox or transport helper the direct child spawned) keeps the pipe open after the child is killed, so the stdout/stderr copy blocks forever and the context deadline is silently defeated.
Variables ¶
This section is empty.
Functions ¶
func NonInteractive ¶
NonInteractive returns an *exec.Cmd detached from the parent's controlling TTY. In the child, /dev/tty cannot be opened, so interactive.CanPromptInteractively() returns false — no env var required.
On Windows the child runs with DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP so it has no inherited console.
func PathScanDirs ¶ added in v0.10.6
func PathScanDirs() []string
PathScanDirs returns the $PATH entries that may be scanned for executables: absolute directories only, in $PATH order.
Every scanner in the CLI must go through this rather than splitting $PATH itself. A relative entry resolves against the process's working directory, which for a git hook or an agent-invoked command is whatever directory the caller happened to be in — usually a repository someone else wrote. A file committed to that repository would then be a binary Entire executes.
Go's own resolver already refuses the worst case: exec.LookPath returns ErrDot for a match found through a "." entry, and exec.Command re-checks a separator-free Path. Neither protection reaches a scanner that resolves by filepath.Glob, because a globbed match arrives as a path WITH separators and never passes through LookPath at all. Dropping the entry at the source is what makes the rule hold for every scanner, whatever it resolves with.
Empty entries are dropped for the same reason: POSIX reads "" as the current directory, so it is the "." case spelled differently.
func SpawnDetached ¶ added in v0.9.0
SpawnDetached re-execs the current executable as a detached, fire-and-forget child running args, surviving the parent's exit (new session on Unix, CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS on Windows, via detachFromTTY). The child runs in dir (os.TempDir() when empty, so the child never holds the parent's working directory), inherits the parent's environment, and has its stdout/stderr discarded. Best-effort: every error is swallowed — callers treat the spawn as advisory background work.
In-process `go test` runs are a no-op: the current executable is the test binary, and re-execing it would fork the whole suite. Tests exercise the call sites through their spawn seams instead.
func TerminateOnCancel ¶ added in v0.10.4
TerminateOnCancel makes cmd and its descendants die when ctx is cancelled. exec.Cmd's default Cancel only kills the direct child, leaving a grandchild (e.g. a sandbox helper or transport helper) alive and holding the output pipe open, which blocks Wait/Run indefinitely past the deadline. A new process group lets Cancel SIGKILL the whole tree; WaitDelay is the backstop that force-closes the pipes if a descendant escapes the group.
cmd must be created with exec.CommandContext so Cancel runs on ctx-done. Do not combine with NonInteractive on the same cmd: NonInteractive sets Setsid, which conflicts with the Setpgid this sets.
Types ¶
This section is empty.