wavetermdev/waveterm · error

error writing to output (AdaptOutputChToStream): %w

Error message

error writing to output (AdaptOutputChToStream): %w

What it means

AdaptOutputChToStream pumps messages from a channel to an io.Writer (usually a stdout pipe). This error wraps any write failure from the underlying writer, aborting the pump loop. The library then drains the remaining channel to avoid blocking producers.

Source

Thrown at pkg/wshutil/wshrpcio.go:35

// * websocket (json packets)

func AdaptStreamToMsgCh(input io.Reader, output chan baseds.RpcInputChType, readCallback func()) error {
	return utilfn.StreamToLines(input, func(line []byte) {
		output <- baseds.RpcInputChType{MsgBytes: line}
	}, readCallback)
}

func AdaptOutputChToStream(outputCh chan []byte, output io.Writer) error {
	drain := false
	defer func() {
		if drain {
			utilfn.DrainChannelSafe(outputCh, "AdaptOutputChToStream")
		}
	}()
	for msg := range outputCh {
		if _, err := output.Write(msg); err != nil {
			drain = true
			return fmt.Errorf("error writing to output (AdaptOutputChToStream): %w", err)
		}
		// write trailing newline
		if _, err := output.Write([]byte{'\n'}); err != nil {
			drain = true
			return fmt.Errorf("error writing trailing newline to output (AdaptOutputChToStream): %w", err)
		}
	}
	return nil
}

func AdaptMsgChToPty(outputCh chan []byte, oscEsc string, output io.Writer) error {
	if len(oscEsc) != 5 {
		panic("oscEsc must be 5 characters")
	}
	for msg := range outputCh {
		barr, err := EncodeWaveOSCBytes(oscEsc, msg)
		if err != nil {
			return fmt.Errorf("error encoding osc message (AdaptMsgChToPty): %w", err)

View on GitHub (pinned to a4447c1563)

Solutions

  1. Check the wrapped %w error for the underlying cause (e.g. EPIPE means the reader closed).
  2. Ensure the reader of the output stream stays open until the channel is fully consumed.
  3. Handle Broken pipe specially in CLI code (exit quietly, like standard Unix tools).
  4. Verify output is not closed or finalized before AdaptOutputChToStream finishes.

Example fix

// before
func caller() {
    AdaptOutputChToStream(ch, os.Stdout)
}
// after
func caller() error {
    err := AdaptOutputChToStream(ch, os.Stdout)
    if err != nil && errors.Is(err, syscall.EPIPE) {
        return nil // consumer closed pipe; exit cleanly
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go has no pre-write validation; check writer usability
if f, ok := output.(*os.File); ok {
    if _, err := f.Stat(); err != nil {
        return fmt.Errorf("output unusable: %w", err)
    }
}

Type guard

func isUsableWriter(w io.Writer) bool {
    c, ok := w.(io.Closer)
    return !ok || c != nil // caller must track lifecycle; inspect concrete type as needed
}

Try / catch

err := AdaptOutputChToStream(ch, output)
if err != nil {
    var perr *fs.PathError
    if errors.As(err, &perr) && errors.Is(perr.Err, syscall.EPIPE) {
        return nil // consumer gone; handle quietly
    }
    return err
}

Prevention

When it happens

Trigger: The io.Writer passed to AdaptOutputChToStream returns an error on Write — e.g. a pipe whose read end is closed, a closed stdout, or a full/closed file descriptor. Returned from within the `for msg := range outputCh` loop.

Common situations: Downstream consumer (process reading the pipe) exits early; CLI output redirected to a closed stream; EPIPE/Broken pipe when the parent process is killed.

Related errors


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