shellenv

package
v1.55.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyToProcess

func ApplyToProcess() error

func CombinedOutputShellCommand

func CombinedOutputShellCommand(cmd *exec.Cmd) ([]byte, error)

CombinedOutputShellCommand is the process-tree-cleaning counterpart to cmd.CombinedOutput. It requires cmd.Stdout and cmd.Stderr to be unset, captures both streams, and delegates lifecycle cleanup to RunShellCommand.

func ConfigureShellCommand

func ConfigureShellCommand(cmd *exec.Cmd)

ConfigureShellCommand isolates cmd in its own process group (Setpgid) and installs a cmd.Cancel that reaps the resulting process tree when cmd's context is cancelled. exec.CommandContext otherwise only kills the direct child PID, leaving grandchildren (a test runner's worker processes, an agent-spawned git/build/editor) running and holding the worktree locked.

A process group is not a process tree, and the difference is not academic. A descendant that calls setsid() gets its own session and its own group, so kill(-leaderPID) provably cannot reach it - and setsid() is what Node's `detached: true` does, which is how Claude Code's CLI Bash tool spawns its shell. Reaching those descendants needs the ppid walk in internal/proctree, which terminateProcessTree performs alongside the group kill.

Cancellation is only half the lifecycle: cmd.Cancel never fires when the command exits on its own (success or failure). Use RunShellCommand, OutputShellCommand, or CombinedOutputShellCommand for one-shot commands, or use StartShellCommand and defer TerminateShellCommandGroup immediately after a successful start when the caller needs manual pipe handling. If a parser reads stdout/stderr until EOF, the goroutine that owns Wait should terminate the group when the leader exits so inherited pipe holders cannot wedge the parser.

Apply this to every long-lived subprocess no-slop spawns on behalf of a cancellable step/agent invocation.

func LoginShell

func LoginShell() string

func OutputShellCommand

func OutputShellCommand(cmd *exec.Cmd) ([]byte, error)

OutputShellCommand is the process-tree-cleaning counterpart to cmd.Output. It requires cmd.Stdout to be unset, captures stdout, and delegates lifecycle cleanup to RunShellCommand.

func Resolve

func Resolve() ([]string, error)

func RunShellCommand

func RunShellCommand(cmd *exec.Cmd) error

RunShellCommand starts cmd with StartShellCommand, waits for it, and terminates any surviving command-group descendants.

Use this instead of cmd.Run after ConfigureShellCommand so clean exits and ordinary errors get the same process-tree cleanup as context cancellation.

func SetProcessRecordDir

func SetProcessRecordDir(dir string) func()

SetProcessRecordDir points descendant tracking at a directory where it persists one record per live leader, and returns a func restoring the previous value.

The daemon calls this once at startup with a directory under NS_HOME. It is unset everywhere else - the CLI and the test suite have no NS_HOME worth writing into, and an empty dir means records are kept in memory only.

func StartShellCommand

func StartShellCommand(cmd *exec.Cmd) error

StartShellCommand starts cmd after ConfigureShellCommand has prepared its process-group lifecycle. Unix needs no extra setup beyond cmd.Start, but the wrapper keeps call sites aligned with Windows job-object setup.

func SupportsInteractive

func SupportsInteractive(shell string) bool

func TerminateShellCommandGroup

func TerminateShellCommandGroup(cmd *exec.Cmd)

TerminateShellCommandGroup terminates the whole process group led by a command configured with ConfigureShellCommand. It is the success/failure-path counterpart to cmd.Cancel: callers defer it right after a successful Start so the group is reaped however Run returns - clean exit, parse error, or wait error - not only on context cancellation.

Why this matters: Setpgid puts each agent/command in its own group, but a test runner's worker pool, a build watcher, or a dev server the agent spawned can outlive the leader. On a normal exit nothing signals the group, so those grandchildren reparent to init and keep running (and keep their memory). They accumulate across runs until the host is out of memory, at which point the OS OOM-killer reaps processes - including the daemon - with an uncatchable SIGKILL, surfacing as "daemon crashed during execution". Reaping the group on every exit path closes that leak so the test step can never take the daemon down.

It is safe to call unconditionally after Wait: the group persists only while a member is alive, so when the leader exited cleanly with no survivors the kill is a harmless no-op (ESRCH). A nil or never-started command is a no-op.

By the time this runs the leader is already dead, so a live snapshot can no longer link anything back to it - the kernel has rewritten its children's ppid to 1. What makes this path work is the descendant union the poller accumulated while the leader was still alive; see tracker_unix.go. Calling it twice is harmless: the union is consumed on the first call.

func WellKnownBinDirs

func WellKnownBinDirs() []string

WellKnownBinDirs returns common binary install locations that should be on PATH for tools like Homebrew, user-local installs, and language package managers (Go, Rust). Non-existent directories are included unchanged because Go's exec.LookPath ignores missing PATH entries - filtering here would require filesystem access in resolution, which complicates testing without adding value for daemon launch.

func WellKnownBinDirsForHome

func WellKnownBinDirsForHome(home string) []string

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL