golang/go · error

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

Error message

bytes.Buffer: UnreadRune: previous operation was not a successful ReadRune

What it means

bytes.Buffer.UnreadRune returns this error when the most recent operation on the buffer was not a successful ReadRune (buffer.go:416-419, guarded by b.lastRead <= opInvalid). The lastRead field is set to opReadRune1..4 only by a successful ReadRune; any other read, any write, or Reset clears it to opInvalid or opRead. UnreadRune is intentionally stricter than UnreadByte.

Source

Thrown at src/bytes/buffer.go:416

	if c < utf8.RuneSelf {
		b.off++
		b.lastRead = opReadRune1
		return rune(c), 1, nil
	}
	r, n := utf8.DecodeRune(b.buf[b.off:])
	b.off += n
	b.lastRead = readOp(n)
	return r, n, nil
}

// 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
	}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Only call UnreadRune when the immediately preceding successful operation was ReadRune — track this in your parser state.
  2. If you need to push back after an arbitrary read, use UnreadByte (which accepts any read op) and re-decode the rune yourself.
  3. Restructure the read loop so ReadRune and UnreadRune are paired locally with no intervening Buffer calls.
  4. Test the parser against inputs that exercise the unread path to confirm the lastRead invariant holds.

Example fix

// before — UnreadRune after ReadByte fails
b, _ := buf.ReadByte()
buf.UnreadRune() // error: previous op was not ReadRune

// after — use ReadRune so UnreadRune is valid, or use UnreadByte
r, _, _ := buf.ReadRune()
if needUnread { buf.UnreadRune(); return }
// or, for byte-level pushback:
b, _ := buf.ReadByte()
if needUnread { buf.UnreadByte() }
Defensive patterns

Strategy: validation

Validate before calling

// Track the last op type so UnreadRune is only called after ReadRune.
type runeReader struct{ b *bytes.Buffer; lastWasReadRune bool }
func (r *runeReader) ReadRune() (ch rune, size int, err error) {
    ch, size, err = r.b.ReadRune()
    r.lastWasReadRune = (err == nil)
    return
}
func (r *runeReader) UnreadRune() error {
    if !r.lastWasReadRune {
        return errors.New("cannot unread: last op was not ReadRune")
    }
    err := r.b.UnreadRune()
    r.lastWasReadRune = false
    return err
}

Try / catch

if err := buf.UnreadRune(); err != nil {
    // err is the documented 'previous operation was not a successful ReadRune'
    // handle by re-reading or skipping the unread
}

Prevention

When it happens

Trigger: Triggered by calling buffer.UnreadRune() when the preceding call was Read/ReadByte/ReadSlice/Write/WriteString/Reset (anything that is not a successful ReadRune), or when no read has happened yet. The check at buffer.go:417 `if b.lastRead <= opInvalid` covers all of those.

Common situations: Calling UnreadRune after a Read loop that used ReadByte or Read rather than ReadRune. Calling UnreadRune after a Write interleaved between ReadRune and UnreadRune. Defensive parsers that call UnreadRune speculatively without tracking the prior operation.

Related errors


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