@@ -50,17 +50,25 @@ func startPty(cmd *exec.Cmd, opt ...StartOption) (retPTY *otherPty, proc Process
5050}
5151return nil ,nil ,xerrors .Errorf ("start: %w" ,err )
5252}
53- // Now that we've started the command, and passed the TTY to it, close our
54- // file so that the other process has the only open file to the TTY. Once
55- // the process closes the TTY (usually on exit), there will be no open
56- // references and the OS kernel returns an error when trying to read or
57- // write to our PTY end. Without this, reading from the process output
58- // will block until we close our TTY.
59- if err := opty .tty .Close ();err != nil {
60- _ = cmd .Process .Kill ()
61- return nil ,nil ,xerrors .Errorf ("close tty: %w" ,err )
53+ if runtime .GOOS == "linux" {
54+ // Now that we've started the command, and passed the TTY to it, close
55+ // our file so that the other process has the only open file to the TTY.
56+ // Once the process closes the TTY (usually on exit), there will be no
57+ // open references and the OS kernel returns an error when trying to
58+ // read or write to our PTY end. Without this (on Linux), reading from
59+ // the process output will block until we close our TTY.
60+ //
61+ // Note that on darwin, reads on the PTY don't block even if we keep the
62+ // TTY file open, and keeping it open seems to prevent race conditions
63+ // where we lose output. Couldn't find official documentation
64+ // confirming this, but I did find a thread of someone else's
65+ // observations: https://developer.apple.com/forums/thread/663632
66+ if err := opty .tty .Close ();err != nil {
67+ _ = cmd .Process .Kill ()
68+ return nil ,nil ,xerrors .Errorf ("close tty: %w" ,err )
69+ }
70+ opty .tty = nil // remove so we don't attempt to close it again.
6271}
63- opty .tty = nil // remove so we don't attempt to close it again.
6472oProcess := & otherProcess {
6573pty :opty .pty ,
6674cmd :cmd ,