labstack/echo · critical

response writer flushing is not supported

Error message

response writer flushing is not supported

What it means

Panicked at request time by delayedStatusWriter.Flush (used in the error-handler path) when the underlying http.ResponseWriter does not support the http.Flusher interface (detected via http.NewResponseController returning http.ErrNotSupported). Flushing is needed when middleware or handlers call c.Response().Flush() while the delayed writer is buffering the status code; if the real writer cannot flush, there is no safe fallback, so Echo panics.

Source

Thrown at response.go:163

	w.committed = true
	w.ResponseWriter.WriteHeader(statusCode)
}

func (w *delayedStatusWriter) Write(data []byte) (int, error) {
	if !w.committed {
		w.committed = true
		if w.status == 0 {
			w.status = http.StatusOK
		}
		w.ResponseWriter.WriteHeader(w.status)
	}
	return w.ResponseWriter.Write(data)
}

func (w *delayedStatusWriter) Flush() {
	err := http.NewResponseController(w.ResponseWriter).Flush()
	if err != nil && errors.Is(err, http.ErrNotSupported) {
		panic(errors.New("response writer flushing is not supported"))
	}
}

func (w *delayedStatusWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
	return http.NewResponseController(w.ResponseWriter).Hijack()
}

func (w *delayedStatusWriter) Unwrap() http.ResponseWriter {
	return w.ResponseWriter
}

// headResponseWriter captures the response that a GET handler would produce for a
// rewritten HEAD request, suppresses the body, and preserves response metadata.
//
// The writer buffers status until the downstream handler returns, so it
// can compute a Content-Length value from the number of body bytes that would have
// been written by the GET handler. If the handler already sets Content-Length
// explicitly, that value is preserved.

View on GitHub (pinned to 05489dc173)

Solutions

  1. Ensure the underlying http.ResponseWriter (or any wrapper in its Unwrap chain) implements http.Flusher (Flush()).
  2. In tests, prefer httptest.NewRecorder() which supports Flusher, or wrap your custom writer to forward Flush to a real implementation.
  3. Avoid calling c.Response().Flush() from middleware that may run inside the error-handling path unless you know the writer supports it.
  4. If using a custom server adapter, document/verify that its ResponseWriter implements http.Flusher.

Example fix

// before: custom writer has no Flush
type memWriter struct{ header http.Header; body bytes.Buffer }
// after: implement http.Flusher
type memWriter struct{ header http.Header; body bytes.Buffer }
func (w *memWriter) Flush() {} // satisfy http.Flusher
func (w *memWriter) Unwrap() http.ResponseWriter { return nil }
Defensive patterns

Strategy: validation

Validate before calling

func supportsFlush(rw http.ResponseWriter) bool {
    // walk the Unwrap chain the same way ResponseController does
    for {
        if _, ok := rw.(http.Flusher); ok { return true }
        if u, ok := rw.(interface{ Unwrap() http.ResponseWriter }); ok {
            rw = u.Unwrap()
            continue
        }
        return false
    }
}

Try / catch

// recover at the server boundary if a non-flushable writer is unavoidable
defer func() {
    if r := recover(); r != nil {
        c.Error(echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("%v", r)))
    }
}()

Prevention

When it happens

Trigger: An error occurs in the handler chain (delayedStatusWriter is engaged) and then something calls Flush on a writer whose chain does not implement http.Flusher — e.g., httptest.ResponseRecorder (which does implement Flush in newer Go, but custom recorders may not) or a bespoke in-memory writer.

Common situations: Tests using a custom ResponseWriter that omits Flush; or a deployment behind a non-standard server adapter whose writer lacks Flusher support, combined with the error-handling path that engages the delayed writer.

Related errors


AI-assisted analysis of labstack/echo@05489dc173 (2026-08-04). Data as JSON: /data/errors/59621e3234433801.json. Report an issue: GitHub.