{"record":{"id":"7f85f7f7e61d26ca","repo":"golang/go","slug":"bytes-reader-unreadrune-previous-operation-was-no","errorCode":null,"errorMessage":"bytes.Reader.UnreadRune: previous operation was not ReadRune","messagePattern":"bytes\\.Reader\\.UnreadRune: previous operation was not ReadRune","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/bytes/reader.go","lineNumber":108,"sourceCode":"\t\treturn 0, 0, io.EOF\n\t}\n\tr.prevRune = int(r.i)\n\tif c := r.s[r.i]; c < utf8.RuneSelf {\n\t\tr.i++\n\t\treturn rune(c), 1, nil\n\t}\n\tch, size = utf8.DecodeRune(r.s[r.i:])\n\tr.i += int64(size)\n\treturn\n}\n\n// UnreadRune complements [Reader.ReadRune] in implementing the [io.RuneScanner] interface.\nfunc (r *Reader) UnreadRune() error {\n\tif r.i <= 0 {\n\t\treturn errors.New(\"bytes.Reader.UnreadRune: at beginning of slice\")\n\t}\n\tif r.prevRune < 0 {\n\t\treturn errors.New(\"bytes.Reader.UnreadRune: previous operation was not ReadRune\")\n\t}\n\tr.i = int64(r.prevRune)\n\tr.prevRune = -1\n\treturn nil\n}\n\n// Seek implements the [io.Seeker] interface.\nfunc (r *Reader) Seek(offset int64, whence int) (int64, error) {\n\tr.prevRune = -1\n\tvar abs int64\n\tswitch whence {\n\tcase io.SeekStart:\n\t\tabs = offset\n\tcase io.SeekCurrent:\n\t\tabs = r.i + offset\n\tcase io.SeekEnd:\n\t\tabs = int64(len(r.s)) + offset\n\tdefault:","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/bytes/reader.go#L90-L126","documentation":"bytes.Reader.UnreadRune returns this error when r.prevRune < 0 (reader.go:107-109), meaning the immediately preceding operation was not a successful ReadRune. prevRune is set to the rune's start index only by ReadRune; Read, ReadByte, Seek, UnreadByte, and Reset all set it to -1. So this fires when position > 0 (the at-beginning check passed) but the last op was not ReadRune.","triggerScenarios":"Triggered by reader.UnreadRune() when r.i > 0 and r.prevRune < 0 (reader.go:107). Common after ReadByte, after Read, after Seek (which clears prevRune at reader.go:117), or after UnreadByte (which clears prevRune at reader.go:81).","commonSituations":"Calling UnreadRune after ReadByte in a mixed byte/rune parser. Calling UnreadRune after a Seek that repositioned the reader. Calling UnreadRune after UnreadByte — the second pushback clears the rune context.","solutions":["Only call UnreadRune when the immediately preceding successful op was ReadRune; track this in parser state.","If you need byte-level pushback after a non-rune op, use UnreadByte and re-decode the rune manually.","Avoid interleaving Seek/UnreadByte between ReadRune and UnreadRune.","Restructure so ReadRune and UnreadRune are paired locally with no intervening reader calls."],"exampleFix":"// before — UnreadRune after ReadByte fails\nb, _ := r.ReadByte()\nr.UnreadRune() // error: previous op was not ReadRune\n\n// after — use ReadRune, or push back at byte level\nr2, _, _ := r.ReadRune()\nif pushBack { r.UnreadRune() }\n// or:\nb, _ := r.ReadByte()\nif pushBack { r.UnreadByte() }","handlingStrategy":"validation","validationCode":"// Track whether the last op was ReadRune before calling UnreadRune.\ntype tracked struct{ r *bytes.Reader; lastWasRune bool }\nfunc (t *tracked) ReadRune() (rune, int, error) {\n    ch, sz, err := t.r.ReadRune()\n    t.lastWasRune = (err == nil)\n    return ch, sz, err\n}\nfunc (t *tracked) UnreadRune() error {\n    if !t.lastWasRune { return errors.New(\"last op was not ReadRune\") }\n    err := t.r.UnreadRune(); t.lastWasRune = false; return err\n}","typeGuard":null,"tryCatchPattern":"if err := r.UnreadRune(); err != nil {\n    if strings.Contains(err.Error(), \"previous operation was not ReadRune\") {\n        // use UnreadByte + manual decode instead\n    } else { return err }\n}","preventionTips":["Only call UnreadRune after a successful ReadRune with no intervening ops.","Avoid interleaving ReadByte, Seek, or UnreadByte between ReadRune and UnreadRune.","If pushback after a non-rune op is needed, use UnreadByte and re-decode."],"tags":["go","bytes","reader","unread","rune","validation"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}