wavetermdev/waveterm · error
failed to get stdout pipe: %w
Error message
failed to get stdout pipe: %w
What it means
After a process controller is created, RunSimpleCommand calls proc.StdoutPipe() to obtain a reader for the child's stdout. This error wraps a failure from that call. For exec-based controllers StdoutPipe rarely fails, but for SSH-session-backed controllers it returns the underlying ssh.Session error — most commonly 'session already started' or a closed/broken session — meaning stdout capture cannot be set up and the command is aborted.
Source
Thrown at pkg/genconn/genconn.go:74
Start() error
Wait() error
Kill()
// 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)View on GitHub (pinned to a4447c1563)
Solutions
- Ensure your ShellProcessController implementation only starts the process in Start(), never in MakeProcessController — pipes must be requested before Start().
- Check the wrapped error; ssh 'session already started' means pipe ordering is wrong in the controller implementation.
- Recreate the controller from a fresh/verified connection if the session was closed.
- Do not reuse a ShellProcessController across multiple runs; create a new one per command.
- If using your own client, conform to genconn.ShellProcessController semantics exactly (pipes before Start).
Example fix
// before (bad controller impl)
func (c *MyController) MakeProcessController(spec genconn.CommandSpec) (genconn.ShellProcessController, error) {
cmd := exec.Command("sh", "-c", spec.Cmd)
cmd.Start() // WRONG: starts before pipes are set
return &MyProc{cmd}, nil
}
// after
func (c *MyController) MakeProcessController(spec genconn.CommandSpec) (genconn.ShellProcessController, error) {
cmd := exec.Command("sh", "-c", spec.Cmd)
return &MyProc{cmd}, nil // only Start() starts it; pipes requested first
} Defensive patterns
Strategy: try-catch
Validate before calling
// Ensure controller contract: pipes must be taken before Start.
// In custom controllers, assert process not started:
if p.started {
return fmt.Errorf("cannot take stdout pipe after start")
} Try / catch
stdout, err := proc.StdoutPipe()
if err != nil {
if strings.Contains(err.Error(), "already started") || strings.Contains(err.Error(), "closed") {
return fmt.Errorf("controller in bad state, rebuild required: %w", err)
}
return fmt.Errorf("stdout pipe: %w", err)
} Prevention
- Always call StdoutPipe before Start()
- Take each pipe exactly once per controller
- Do not reuse controllers across commands
- Rebuild the controller from a fresh session if any pipe call fails
- Implement ShellProcessController contract faithfully in custom clients
When it happens
Trigger: proc.StdoutPipe() errors: calling pipes after Start() on some implementations (SSH sessions reject pipe setup after start), the ssh session was closed between MakeProcessController and StdoutPipe, or the local exec.Cmd was misconfigured by the client.
Common situations: A custom ShellProcessController implementation that starts the process inside MakeProcessController (violating the interface contract that Start() comes later); a stale SSH connection; reusing one controller for multiple commands.
Related errors
- failed to get stderr pipe: %w
- failed to start durable shell: %w
- failed to create stdout pipe: %w
- failed to create stderr pipe: %w
- failed to create process controller: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/47c3b867c543e4fc.
Report an issue: GitHub.