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

  1. Verify the controller implementation allows both StdoutPipe and StderrPipe before Start(), and does not start early.
  2. Inspect the wrapped error for 'session already started' or 'use of closed connection' and fix the ordering / reconnect accordingly.
  3. Check file descriptor limits (ulimit -n) if pipe allocation fails under load.
  4. Create a fresh controller per command run instead of reusing one.
  5. 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

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


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/5f5eac49e5c5ee13. Report an issue: GitHub.