From b714ee14d508be649eea01d2549d527c8ba845c9 Mon Sep 17 00:00:00 2001 From: Craig Williams Date: Fri, 16 Oct 2026 08:52:45 +0100 Subject: [PATCH] Fixed ws dial input error being dropped --- internal/cli/ws/dial.go | 10 +++++++++- internal/cli/ws/dial_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/internal/cli/ws/dial.go b/internal/cli/ws/dial.go index 49257e4..1556868 100644 --- a/internal/cli/ws/dial.go +++ b/internal/cli/ws/dial.go @@ -321,14 +321,17 @@ func interact( }() // Reading input can't be interrupted, so it has a goroutine of its own, - // left blocked if ctx is cancelled first. + // left blocked if ctx is cancelled first. Its error, if any, is sent + // before lines is closed. lines := make(chan string) + inputErr := make(chan error, 1) go func() { defer close(lines) scanner := bufio.NewScanner(input) for scanner.Scan() { lines <- scanner.Text() } + inputErr <- scanner.Err() }() for { @@ -339,6 +342,11 @@ func interact( return err case line, ok := <-lines: if !ok { + err := <-inputErr + if err != nil { + return fmt.Errorf("reading input: %w", err) + } + // The end of input: stop sending, keep listening. lines = nil continue diff --git a/internal/cli/ws/dial_test.go b/internal/cli/ws/dial_test.go index 5b40568..8a26804 100644 --- a/internal/cli/ws/dial_test.go +++ b/internal/cli/ws/dial_test.go @@ -3,6 +3,7 @@ package ws import ( "bytes" "context" + "errors" "io" "log/slog" "net/http" @@ -281,6 +282,34 @@ func TestDialStdin(t *testing.T) { } } +// Without --message, a failure reading stdin ends dial with an error, unlike +// the end of stdin. +func TestDialStdinError(t *testing.T) { + _, url := wsServer(t) + stdin, input := io.Pipe() + out := &syncBuffer{} + + done := make(chan error, 1) + go func() { + done <- dial(context.Background(), stdin, out, dialSettings{ + url: url, + timeout: time.Second, + }) + }() + + waitFor(t, out, `< {"type":"welcome"`) + input.CloseWithError(errors.New("disk on fire")) + + select { + case err := <-done: + if err == nil || !strings.Contains(err.Error(), "reading input: disk on fire") { + t.Fatalf("dial = %v; want the input error", err) + } + case <-time.After(time.Second): + t.Fatal("dial did not return after the input error") + } +} + // Without --message, dial ends when the server closes the connection. func TestDialStdinServerClose(t *testing.T) { hub, url := wsServer(t)