gofiber/fiber · error

failed to write response body to writer: %w

Error message

failed to write response body to writer: %w

What it means

Returned by Response.Save (client/response.go:178) when io.Copy fails while streaming the response body into a caller-supplied io.Writer. Save accepts an io.Writer (e.g. *os.File, *bytes.Buffer, a network connection) and copies BodyStream() into it; any write error on the destination, or read error on the stream, is wrapped here.

Source

Thrown at client/response.go:178

		// Create and write to file
		outFile, err := os.Create(file)
		if err != nil {
			return fmt.Errorf("failed to create file: %w", err)
		}
		defer func() { _ = outFile.Close() }() //nolint:errcheck // not needed

		// Use BodyStream() which handles both streaming and non-streaming cases
		if _, err = io.Copy(outFile, r.BodyStream()); err != nil {
			return fmt.Errorf("failed to write response body to file: %w", err)
		}

		return nil

	case io.Writer:
		// Use BodyStream() which handles both streaming and non-streaming cases
		if _, err := io.Copy(p, r.BodyStream()); err != nil {
			return fmt.Errorf("failed to write response body to writer: %w", err)
		}
		// Close the writer if it implements io.WriteCloser
		if pc, ok := p.(io.WriteCloser); ok {
			_ = pc.Close() //nolint:errcheck // not needed
		}

		return nil

	default:
		return ErrNotSupportSaveMethod
	}
}

// Reset clears the Response object, making it ready for reuse.
func (r *Response) Reset() {
	r.client = nil
	r.request = nil

View on GitHub (pinned to 9a4c7e57fe)

Solutions

  1. Ensure the destination Writer's downstream stays alive for the full copy (keep the connection/reader open).
  2. Use an unbounded or sufficiently large buffer if the destination can stall.
  3. Inspect the wrapped error to distinguish a destination-write error from a source-stream error.
  4. Retry the upstream request if the source stream broke.

Example fix

// before — piping to a connection that may close mid-stream
if err := resp.Save(downstreamConn); err != nil {
    log.Print(err) // half-written downstream response
}

// after — buffer then forward, or handle the broken-pipe error explicitly
var buf bytes.Buffer
if err := resp.Save(&buf); err != nil {
    return fmt.Errorf("read response body: %w", err)
}
if _, err := downstreamConn.Write(buf.Bytes()); err != nil {
    return fmt.Errorf("forward body: %w", err)
}
Defensive patterns

Strategy: try-catch

Type guard

// Ensure the destination implements io.Writer and is not prematurely closed.
func liveWriter(w any) bool {
    _, ok := w.(io.Writer)
    return ok
}

Try / catch

if err := resp.Save(w); err != nil {
    if strings.Contains(err.Error(), "write response body to writer") {
        // downstream writer failed — buffer and handle separately
    }
    return err
}

Prevention

When it happens

Trigger: Calling resp.Save(writer) where writer is a network connection that closed, a pipe whose reader exited, a buffer that hit a hard cap, or any io.Writer whose Write returns an error. Also triggered when the body stream errors during the copy (server cut the connection, corrupt gzip).

Common situations: Streaming a download straight to an HTTP client whose connection dropped; piping through a writer whose downstream consumer died; a bounded buffer overflowing; proxying a large response that the next hop rejects mid-stream.

Related errors


AI-assisted analysis of gofiber/fiber@9a4c7e57fe (2026-08-04). Data as JSON: /data/errors/ce61eb421f2c2b6c.json. Report an issue: GitHub.