wavetermdev/waveterm · error

failed to create stdout pipe: %w

Error message

failed to create stdout pipe: %w

What it means

runBuilderApp launches a builder subprocess via os/exec and wires up its I/O. This error wraps the failure from cmd.StdoutPipe(), which Go's exec package returns when the process has already been started or the OS cannot allocate a pipe. It is thrown before the builder process starts, so no child process exists yet.

Source

Thrown at pkg/buildercontroller/buildercontroller.go:340

	}
	for k, v := range secretEnv {
		builderEnv[k] = v
	}

	cmd := exec.Command(appBinPath)
	cmd.Env = append(os.Environ(), "TSUNAMI_CLOSEONSTDIN=1")

	if wavebase.IsDevMode() {
		cmd.Env = append(cmd.Env, "TSUNAMI_CORS="+tsunamiutil.DevModeCorsOrigins)
	}

	for key, value := range builderEnv {
		cmd.Env = append(cmd.Env, key+"="+value)
	}

	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		return nil, fmt.Errorf("failed to create stdout pipe: %w", err)
	}

	stderrPipe, err := cmd.StderrPipe()
	if err != nil {
		return nil, fmt.Errorf("failed to create stderr pipe: %w", err)
	}

	stdinPipe, err := cmd.StdinPipe()
	if err != nil {
		return nil, fmt.Errorf("failed to create stdin pipe: %w", err)
	}

	portChan := make(chan int, 1)
	portFound := false

	bc.outputBuffer.SetLineCallback(func(line string) {
		if !portFound {
			if port := build.ParseTsunamiPort(line); port > 0 {

View on GitHub (pinned to a4447c1563)

Solutions

  1. Ensure cmd.Start() is called only once and only after all pipes are created
  2. Check the wrapped error (%w) — if 'too many open files', fix fd leaks or raise ulimit
  3. Re-run the build; transient fd exhaustion clears once other handles are released

Example fix

// before
cmd.Start()
stdoutPipe, err := cmd.StdoutPipe() // fails: process already started
// after
stdoutPipe, err := cmd.StdoutPipe()
if err != nil { return nil, fmt.Errorf("failed to create stdout pipe: %w", err) }
err = cmd.Start()
Defensive patterns

Strategy: try-catch

Validate before calling

if cmd.Process != nil { panic("cmd already started") } // create pipes only on a fresh exec.Cmd

Try / catch

process, err := bc.runBuilderApp(ctx, ...)
if err != nil {
    if strings.Contains(err.Error(), "failed to create stdout pipe") {
        // fd exhaustion or double-start; inspect wrapped cause with errors.Unwrap
    }
    return err
}

Prevention

When it happens

Trigger: cmd.StdoutPipe() returns non-nil err — practically only when cmd.Start() was already called on the same Cmd, or on rare OS pipe-allocation failure (fd exhaustion).

Common situations: Code accidentally starting the command twice; extreme fd exhaustion (ulimit -n hit) after leaking pipes/files in a long-running server; reusing a BuilderController Cmd struct incorrectly.

Related errors


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