diff --git a/internal/cli/logs.go b/internal/cli/logs.go index 08b64e7..488b1b3 100644 --- a/internal/cli/logs.go +++ b/internal/cli/logs.go @@ -334,7 +334,12 @@ func runLogStreams(ctx context.Context, streams []logStream, out, errOut io.Writ for _, stream := range streams { one := &running{logStream: stream, lines: make(chan string, 256), stderr: &limitedBuffer{limit: 4 << 10}} + // The ssh commands come wired to this process's terminal (vssh's + // command()), and a stream that already has somewhere to write + // cannot also be read: exec refuses with "Stdout already set". The + // lines are read here, so the output is taken back first. stream.cmd.Stdin = nil + stream.cmd.Stdout = nil stream.cmd.Stderr = one.stderr pipe, err := stream.cmd.StdoutPipe() if err != nil { diff --git a/internal/cli/logs_test.go b/internal/cli/logs_test.go index b43da6d..d533a6a 100644 --- a/internal/cli/logs_test.go +++ b/internal/cli/logs_test.go @@ -3,6 +3,7 @@ package cli import ( "context" "encoding/json" + "os" "os/exec" "strconv" "strings" @@ -189,6 +190,22 @@ func TestSeveralMachinesAreReadInOrderOfTime(t *testing.T) { } } +// The ssh commands the target builds write to this process's terminal; the +// reader takes their output back rather than failing on "Stdout already +// set", which is what `vallic logs` answered on its first real run. +func TestStreamWiredToTheTerminalIsRead(t *testing.T) { + cmd := exec.Command("printf", "%s\n", "2026-11-13T10:00:00.000Z [cron] ran") + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + var out, errOut strings.Builder + lines, err := runLogStreams(context.Background(), []logStream{{cmd: cmd}}, &out, &errOut, logsRequest{}) + if err != nil || lines != 1 || out.String() != "2026-11-13T10:00:00.000Z [cron] ran\n" { + t.Errorf("a stream wired to the terminal read %q, %d lines, %v", out.String(), lines, err) + } +} + // As objects, a line says which machine it is from inside the object, and // is ordered by the time it holds. func TestLinesAsObjectsNameTheirMachine(t *testing.T) {