13 "mvdan.cc/sh/v3/interp"
16// defaultKillTimeout matches mvdan's DefaultExecHandler default. Extracted
17// so the coupling to upstream is explicit rather than a buried literal.
18const defaultKillTimeout = 2 * time.Second
20// isolateProcess sets SysProcAttr so the child runs in its own session,
21// fully detached from Crush's controlling terminal. This prevents shells
22// like zsh from grabbing the TTY for job control, which would cause
23// "suspended (tty input)" and corrupt Bubble Tea's terminal rendering.
24// It also prevents child processes from sending signals to Crush's process
26func isolateProcess(cmd *exec.Cmd) {
27 if cmd.SysProcAttr == nil {
28 cmd.SysProcAttr = &syscall.SysProcAttr{}
30 cmd.SysProcAttr.Setsid = true
33// processGroupExecHandler returns an ExecHandlerFunc that replaces
34// interp.DefaultExecHandler with one that fully isolates child processes
35// from Crush's session and controlling terminal.
37// Without this, shells like zsh that enable job control when sourcing
38// framework files will attempt to take over the TTY, causing SIGTTIN/SIGTTOU
39// signals and corrupting the parent terminal state.
41// The implementation mirrors interp.DefaultExecHandler with two additions:
42// isolateProcess(&cmd) after construction, and negative-PID signal targeting
43// in the cancellation callback so the entire child process group is killed.
44func processGroupExecHandler(killTimeout time.Duration) interp.ExecHandlerFunc {
45 return func(ctx context.Context, args []string) error {
46 hc := interp.HandlerCtx(ctx)
47 path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0])
49 fmt.Fprintln(hc.Stderr, err)
50 return interp.ExitStatus(127)
56 Env: execEnvList(hc.Env),
66 stopf := context.AfterFunc(ctx, func() {
68 _ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
71 // Signal the child's process group (negative PID) so
72 // grandchildren also receive it.
73 _ = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT)
74 time.Sleep(killTimeout)
75 _ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
82 return exitStatusFromError(ctx, hc.Stderr, err)
86// exitStatusFromError translates an exec error into an interp exit status,
87// matching the conventions of interp.DefaultExecHandler. Extracted so the
88// platform-specific exec handler stays focused on isolation mechanics.
89func exitStatusFromError(ctx context.Context, stderr io.Writer, err error) error {
93 switch err := err.(type) {
95 if status, ok := err.Sys().(syscall.WaitStatus); ok && status.Signaled() {
99 return interp.ExitStatus(128 + uint8(status.Signal()))
101 return interp.ExitStatus(uint8(err.ExitCode()))
103 fmt.Fprintf(stderr, "%v\n", err)
104 return interp.ExitStatus(127)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list