wavetermdev/waveterm · error
failed to get stderr pipe: %w
Error message
failed to get stderr pipe: %w
What it means
RunSimpleCommand calls proc.StderrPipe() to capture the child's stderr; this error wraps a failure of that call, after stdout was successfully piped. Same family as the stdout-pipe error: it means output capture could not be set up, typically because the underlying session/exec object is in the wrong state (already started or closed), and the command is aborted before Start().
Source
Thrown at pkg/genconn/genconn.go:78
// these are not required to be called, if they are not called, the impl will set to discard output
StdinPipe() (io.WriteCloser, error)
StdoutPipe() (io.Reader, error)
StderrPipe() (io.Reader, error)
}
func RunSimpleCommand(ctx context.Context, client ShellClient, spec CommandSpec) (string, string, error) {
proc, err := client.MakeProcessController(spec)
if err != nil {
return "", "", fmt.Errorf("failed to create process controller: %w", err)
}
stdout, err := proc.StdoutPipe()
if err != nil {
return "", "", fmt.Errorf("failed to get stdout pipe: %w", err)
}
stderr, err := proc.StderrPipe()
if err != nil {
return "", "", fmt.Errorf("failed to get stderr pipe: %w", err)
}
if err := proc.Start(); err != nil {
return "", "", fmt.Errorf("failed to start process: %w", err)
}
stdoutBuf := syncbuf.MakeSyncBuffer()
stderrBuf := syncbuf.MakeSyncBuffer()
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
io.Copy(stdoutBuf, stdout)
}()
go func() {
defer wg.Done()View on GitHub (pinned to a4447c1563)
Solutions
- Verify the controller implementation allows both StdoutPipe and StderrPipe before Start(), and does not start early.
- Inspect the wrapped error for 'session already started' or 'use of closed connection' and fix the ordering / reconnect accordingly.
- Check file descriptor limits (ulimit -n) if pipe allocation fails under load.
- Create a fresh controller per command run instead of reusing one.
- If the SSH connection is stale, reconnect and retry the command.
Example fix
// before
stderr, err := proc.StderrPipe()
if err != nil {
return "", "", err
}
// after
stderr, err := proc.StderrPipe()
if err != nil {
if isSessionClosed(err) {
if rerr := conn.Reconnect(ctx); rerr == nil {
proc, err = client.MakeProcessController(spec)
if err == nil {
return retryRun(ctx, client, spec)
}
}
}
return "", "", err
} Defensive patterns
Strategy: try-catch
Validate before calling
// take both pipes before starting:
stdout, err := proc.StdoutPipe()
if err != nil { return err }
stderr, err := proc.StderrPipe()
if err != nil { return err } Try / catch
stderr, err := proc.StderrPipe()
if err != nil {
log.Printf("stderr pipe failed (stdout ok): %v", err)
// fallback: proceed with discard if stderr capture is optional
stderr = io.Discard
} Prevention
- Set up stdout and stderr pipes back-to-back before Start()
- Only claim each pipe once per process
- Handle fd exhaustion: raise ulimit -n on busy hosts
- Reconnect stale SSH sessions before spawning
- Prefer genconn.RunSimpleCommand over manual pipe orchestration
When it happens
Trigger: proc.StderrPipe() returns an error: SSH session already started or closed, controller implementation rejected a second pipe request, or internal pipe allocation failure on the exec.Cmd.
Common situations: Custom controller implementations that limit pipe calls to once or start the process prematurely; SSH sessions that timed out between pipe setup calls; extremely fd-constrained systems failing to allocate pipes.
Related errors
- failed to create stderr pipe: %w
- failed to get stdout pipe: %w
- failed to start durable shell: %w
- failed to create stdout pipe: %w
- failed to create process controller: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/5f5eac49e5c5ee13.
Report an issue: GitHub.