diff --git a/Lib/codecs.py b/Lib/codecs.py index af6ab031157e79..eaa5da00630cd5 100644 --- a/Lib/codecs.py +++ b/Lib/codecs.py @@ -525,6 +525,15 @@ def read(self, size=-1, chars=-1, firstline=False): self.charbuffer += newchars # there was no data available if not newdata: + # The stream is exhausted. If there are leftover bytes in + # bytebuffer, they form an incomplete character. Decode them + # now so that the error handler is applied (strict raises, + # replace emits the replacement character, ignore drops them). + if self.bytebuffer: + newchars, decodedbytes = self.decode( + self.bytebuffer, self.errors) + self.bytebuffer = self.bytebuffer[decodedbytes:] + self.charbuffer += newchars break if chars < 0: # Return everything we've got diff --git a/Misc/NEWS.d/next/Library/2026-10-25-21-18-27.gh-issue-160309.wJyl0Q.rst b/Misc/NEWS.d/next/Library/2026-10-25-21-18-27.gh-issue-160309.wJyl0Q.rst new file mode 100644 index 00000000000000..0c34090b71e44c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-25-21-18-27.gh-issue-160309.wJyl0Q.rst @@ -0,0 +1 @@ +Fix codecs.StreamReader.read() silently dropping an incomplete character at the end of the stream.