detached e77fed7

fix(sshstdio): bound how long mux close waits for a lingering grandchild

commit
e77fed7b3908fbfc903ba2e5b378864c4e79493e
author
brice · · 2026-09-23 21:37 UTC
parent
d2968dd
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +5 -0
M internal/adapters/sshstdio/mux.go +5 -0
@@ -46,6 +46,7 @@ import (
4646 "os/exec"
4747 "strings"
4848 "sync"
49+ "time"
4950 "unicode"
5051 "unicode/utf8"
5152
@@ -166,6 +167,10 @@ func dialMuxContext(ctx context.Context, spec CommandSpec, logger *slog.Logger,
166167 }
167168 stderr := newCappedDiagnostic(muxStderrLimit)
168169 cmd.Stderr = stderr
170+ // A grandchild (for example ssh's ProxyCommand) can outlive a killed
171+ // child and hold the stderr pipe open; bound how long Wait keeps copying
172+ // from it so Close never waits for that grandchild to exit.
173+ cmd.WaitDelay = time.Second
169174 if err := cmd.Start(); err != nil {
170175 _ = stdin.Close()
171176 return nil, fmt.Errorf("sshstdio: mux start %s: %w", spec.Path, err)
@@ -46,6 +46,7 @@ import (
46 "os/exec"46 "os/exec"
47 "strings"47 "strings"
48 "sync"48 "sync"
49+ "time"
49 "unicode"50 "unicode"
50 "unicode/utf8"51 "unicode/utf8"
51 52
@@ -166,6 +167,10 @@ func dialMuxContext(ctx context.Context, spec CommandSpec, logger *slog.Logger,
166 }167 }
167 stderr := newCappedDiagnostic(muxStderrLimit)168 stderr := newCappedDiagnostic(muxStderrLimit)
168 cmd.Stderr = stderr169 cmd.Stderr = stderr
170+ // A grandchild (for example ssh's ProxyCommand) can outlive a killed
171+ // child and hold the stderr pipe open; bound how long Wait keeps copying
172+ // from it so Close never waits for that grandchild to exit.
173+ cmd.WaitDelay = time.Second
169 if err := cmd.Start(); err != nil {174 if err := cmd.Start(); err != nil {
170 _ = stdin.Close()175 _ = stdin.Close()
171 return nil, fmt.Errorf("sshstdio: mux start %s: %w", spec.Path, err)176 return nil, fmt.Errorf("sshstdio: mux start %s: %w", spec.Path, err)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close