Skip to content

Commit 2ebdc57

Browse files
authored
Fixed CLI hanging when terminal is closed (#897)
## What was changed - Added `SIGHUP` signal handling to gracefully exit when the terminal disconnects - Added broken pipe detection in the printer to exit cleanly instead of hanging when stdout becomes unavailable ## Why? When a user closes a terminal tab while the CLI is running (or an SSH session drops), the CLI process could get stuck in an uninterruptible state. This happened because: 1. The CLI wasn't listening for `SIGHUP` (the signal sent when a terminal closes) 2. Writes to stdout would block forever when the terminal's file descriptors were revoked Now the CLI exits gracefully in both cases instead of requiring a force-kill. ## Checklist 1. The bug fix discovered during development 2. How was this tested: - Ran CLI commands and closed the terminal tab mid-execution - Verified the process exits cleanly instead of hanging in `UE` (uninterruptible sleep) state 3. Any docs updates needed? - No
1 parent aa925d3 commit 2ebdc57

File tree

3 files changed

+29
-1
lines changed

3 files changed

+29
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@
1111
/.zed
1212
/.claude
1313
*~
14+
15+
.DS_Store

internal/printer/printer.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package printer
33
import (
44
"encoding/base64"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"io"
9+
"os"
810
"reflect"
911
"slices"
1012
"strconv"
1113
"strings"
14+
"syscall"
1215
"time"
1316

1417
"github.com/fatih/color"
@@ -189,8 +192,25 @@ func (p *Printer) PrintStructuredTableIter(
189192
}
190193
}
191194

195+
// isBrokenPipeError returns true if the error indicates a broken pipe or
196+
// disconnected terminal (e.g., when the terminal is closed while writing).
197+
func isBrokenPipeError(err error) bool {
198+
if errors.Is(err, syscall.EPIPE) {
199+
return true
200+
}
201+
// Check for "broken pipe" in error message as a fallback
202+
if err != nil && strings.Contains(err.Error(), "broken pipe") {
203+
return true
204+
}
205+
return false
206+
}
207+
192208
func (p *Printer) write(b []byte) {
193209
if _, err := p.Output.Write(b); err != nil {
210+
// Exit gracefully on broken pipe (terminal disconnected)
211+
if isBrokenPipeError(err) {
212+
os.Exit(0)
213+
}
194214
panic(err)
195215
}
196216
}
@@ -201,6 +221,10 @@ func (p *Printer) writeStr(s string) {
201221

202222
func (p *Printer) writef(s string, v ...any) {
203223
if _, err := fmt.Fprintf(p.Output, s, v...); err != nil {
224+
// Exit gracefully on broken pipe (terminal disconnected)
225+
if isBrokenPipeError(err) {
226+
os.Exit(0)
227+
}
204228
panic(err)
205229
}
206230
}

internal/temporalcli/commands.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ func NewCommandContext(ctx context.Context, options CommandOptions) (*CommandCon
108108
}
109109

110110
// Setup interrupt handler
111-
ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
111+
// Include SIGHUP to handle terminal disconnection gracefully
112+
// Without this, the process may hang when the terminal is closed
113+
ctx, stop := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
112114
cctx.Context = ctx
113115
return cctx, stop, nil
114116
}

0 commit comments

Comments
 (0)