@@ -50,17 +50,25 @@ func startPty(cmd *exec.Cmd, opt ...StartOption) (retPTY *otherPty, proc Process
50
50
}
51
51
return nil , nil , xerrors .Errorf ("start: %w" , err )
52
52
}
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.
62
71
}
63
- opty .tty = nil // remove so we don't attempt to close it again.
64
72
oProcess := & otherProcess {
65
73
pty : opty .pty ,
66
74
cmd : cmd ,
0 commit comments