golang/go · critical

bufio: reader returned negative count from Read

Error message

bufio: reader returned negative count from Read

What it means

Go's bufio package panics with this message inside Reader.fill() when the underlying io.Reader's Read method returns a negative byte count n. The io.Reader contract (documented in package io) mandates 0 <= n <= len(p); a negative value violates the contract so severely that bufio treats it as a programming bug in the reader implementation and aborts rather than continuing with corrupted buffer indices. This is an unrecoverable panic, not a returned error.

Source

Thrown at src/bufio/bufio.go:96

	if b == r {
		return
	}
	if b.buf == nil {
		b.buf = make([]byte, defaultBufSize)
	}
	b.reset(b.buf, r)
}

func (b *Reader) reset(buf []byte, r io.Reader) {
	*b = Reader{
		buf:          buf,
		rd:           r,
		lastByte:     -1,
		lastRuneSize: -1,
	}
}

var errNegativeRead = errors.New("bufio: reader returned negative count from Read")

// fill reads a new chunk into the buffer.
func (b *Reader) fill() {
	// Slide existing data to beginning.
	if b.r > 0 {
		copy(b.buf, b.buf[b.r:b.w])
		b.w -= b.r
		b.r = 0
	}

	if b.w >= len(b.buf) {
		panic("bufio: tried to fill full buffer")
	}

	// Read new data: try a limited number of times.
	for i := maxConsecutiveEmptyReads; i > 0; i-- {
		n, err := b.rd.Read(b.buf[b.w:])
		if n < 0 {

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Inspect the concrete io.Reader passed to bufio.NewReader — find its Read method and fix the negative return; Read must return n >= 0 and a non-nil error to signal trouble.
  2. If you cannot fix the upstream reader, wrap it in an adapter that clamps n to 0 and synthesizes an error when the inner Read returns negative.
  3. Add a unit test that feeds the suspect reader into a bufio.Reader and asserts no panic under error paths.
  4. Run `go vet` and review the io.Reader implementation against the contract in `io` package docs.

Example fix

// before — buggy reader returns negative n
func (b *BadReader) Read(p []byte) (int, error) {
    if b.err != nil {
        return -1, b.err // WRONG: violates io.Reader contract
    }
    return b.src.Read(p)
}

// after — return 0 with the error
func (b *BadReader) Read(p []byte) (int, error) {
    if b.err != nil {
        return 0, b.err
    }
    return b.src.Read(p)
}
Defensive patterns

Strategy: validation

Validate before calling

// Wrap any custom io.Reader before passing to bufio to guarantee the Read contract.
type safeReader struct{ io.Reader }
func (s safeReader) Read(p []byte) (int, error) {
    n, err := s.Reader.Read(p)
    if n < 0 {
        return 0, fmt.Errorf("underlying reader returned negative count %d: %w", n, err)
    }
    return n, err
}
// Usage:
// r := bufio.NewReader(safeReader{myCustomReader})

Type guard

// Contract checker for io.Reader implementations (use in tests).
func assertReaderContract(r io.Reader) error {
    probe := make([]byte, 8)
    n, err := r.Read(probe)
    if n < 0 || n > len(probe) {
        return fmt.Errorf("reader violates contract: n=%d", n)
    }
    return err
}

Try / catch

// Go has no try/catch; use defer/recover only as a last-resort guard around bufio use
defer func() {
    if r := recover(); r != nil {
        if msg, ok := r.(string); ok && strings.Contains(msg, "negative count from Read") {
            // log and exit gracefully; the underlying reader is buggy
        }
        panic(r) // re-panic unknown
    }
}()
n, err := bufioReader.Read(buf)

Prevention

When it happens

Trigger: Triggered when bufio.Reader.fill() calls b.rd.Read(buf) and the returned n is < 0 (bufio.go:114-115). Any call that forces a buffer refill — Read, ReadByte, ReadRune, ReadString, Peek, Discard, WriteTo — can reach fill() and trip the panic. The immediate cause is always the wrapped io.Reader returning a negative count.

Common situations: A custom io.Reader implementation has a bug computing the return count (e.g., returns -1 to signal an internal error instead of returning 0 with a proper error). A wrapped reader that subtracts offsets incorrectly, or a middleware reader (compressed/encrypted stream) whose internal accounting overflows. Rarely seen with stdlib readers; almost always a hand-rolled Reader.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/8f73465709570e64. Report an issue: GitHub.