golang/go · error

bytes.Buffer: UnreadByte: previous operation was not a succe

Error message

bytes.Buffer: UnreadByte: previous operation was not a successful read

What it means

bytes.Buffer.UnreadByte returns errUnreadByte when the most recent operation was not a successful read (buffer.go:425-428, guarded by b.lastRead == opInvalid). lastRead is opInvalid after writes, after Reset, before any read, or after an unread has already been consumed. Unlike UnreadRune, UnreadByte accepts any successful read op (opRead or opReadRuneX).

Source

Thrown at src/bytes/buffer.go:425

}

// UnreadRune unreads the last rune returned by [Buffer.ReadRune].
// If the most recent read or write operation on the buffer was
// not a successful [Buffer.ReadRune], UnreadRune returns an error.  (In this regard
// it is stricter than [Buffer.UnreadByte], which will unread the last byte
// from any read operation.)
func (b *Buffer) UnreadRune() error {
	if b.lastRead <= opInvalid {
		return errors.New("bytes.Buffer: UnreadRune: previous operation was not a successful ReadRune")
	}
	if b.off >= int(b.lastRead) {
		b.off -= int(b.lastRead)
	}
	b.lastRead = opInvalid
	return nil
}

var errUnreadByte = errors.New("bytes.Buffer: UnreadByte: previous operation was not a successful read")

// UnreadByte unreads the last byte returned by the most recent successful
// read operation that read at least one byte. If a write has happened since
// the last read, if the last read returned an error, or if the read read zero
// bytes, UnreadByte returns an error.
func (b *Buffer) UnreadByte() error {
	if b.lastRead == opInvalid {
		return errUnreadByte
	}
	b.lastRead = opInvalid
	if b.off > 0 {
		b.off--
	}
	return nil
}

// ReadBytes reads until the first occurrence of delim in the input,
// returning a slice containing the data up to and including the delimiter.

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Ensure the immediately preceding successful Buffer operation was a read before calling UnreadByte.
  2. Avoid double-unread: only one pushback is valid; track it in parser state.
  3. If you must unread after a write, restructure so the write happens after the unread, or re-read from a known offset.
  4. Pair each UnreadByte call with a specific preceding read in the code so the invariant is locally obvious.

Example fix

// before — write between read and UnreadByte clears lastRead
b, _ := buf.ReadByte()
buf.WriteByte('x')
buf.UnreadByte() // error: previous op was not a successful read

// after — unread before the write, or re-read
b, _ := buf.ReadByte()
if pushBack { buf.UnreadByte() }
buf.WriteByte('x')
Defensive patterns

Strategy: validation

Validate before calling

// Pair UnreadByte with a known preceding read.
if _, err := buf.ReadByte(); err != nil { return err }
// ... decision ...
if shouldPushBack {
    if err := buf.UnreadByte(); err != nil {
        // unexpected: lastRead should be opRead here
        return err
    }
}

Try / catch

if err := buf.UnreadByte(); err != nil {
    if err.Error() == "bytes.Buffer: UnreadByte: previous operation was not a successful read" {
        // re-read from a known offset instead of unread
    } else {
        return err
    }
}

Prevention

When it happens

Trigger: Triggered by calling buffer.UnreadByte() when lastRead == opInvalid: after a Write/WriteString/WriteByte, after Reset, before the first read, or after a prior UnreadByte/UnreadRune already reset lastRead to opInvalid.

Common situations: Interleaving a write between a read and UnreadByte. Calling UnreadByte twice in a row (the second call sees opInvalid). Speculative unread in a tokenizer without verifying the prior op was a read.

Related errors


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