io.TextIOWrapper.reconfigure(errors=...) Mid-Write Silently Drops Characters (tested)
io.TextIOWrapper.reconfigure() can change errors on an already-open text stream, the same way it can change encoding. On the read side it enforces a safety rule: calling reconfigure() after any read() has happened raises UnsupportedOperation. The write side has no such restriction for errors= either -- you can switch from errors='strict' to errors='ignore' after writing any amount of text, and it takes effect immediately for everything written afterward, with no error or warning raised at write time. Unlike the encoding= version of this bug, which mixes two valid-but-different byte sequences, this one is quietly destructive: errors='ignore' means characters that can't be encoded are dropped, not flagged, so the file ends up permanently missing data with nothing in the write path telling you.
1. Strict mode correctly raises on an unencodable character
import io
buf = io.BytesIO()
text = io.TextIOWrapper(buf, encoding="ascii", errors="strict")
text.write("hello ")
try:
text.write("café")
except UnicodeEncodeError as e:
print("strict write fails ->", e)
strict write fails -> 'ascii' codec can't encode character '\xe9' in position 3: ordinal not in range(128)
Measured: writing a character outside the ASCII range under errors='strict' (the default) raises UnicodeEncodeError immediately, which is the expected, documented behavior.
2. reconfigure(errors='ignore') after that failure succeeds silently
text.reconfigure(errors="ignore")
text.write("café")
text.flush()
print(buf.getvalue())
b'hello caf'
Measured: after reconfiguring to errors='ignore', writing the exact same unencodable string raises nothing -- but the é is silently dropped. The buffer contains b'hello caf', not b'hello café' in any encoding and not an error: the character is simply gone, with no exception, no warning, and no return value indicating anything was lost.
3. The read side blocks the identical reconfigure() call
reader = io.TextIOWrapper(io.BytesIO(b"hello"), encoding="utf-8", errors="strict")
reader.read(1)
try:
reader.reconfigure(errors="ignore")
except Exception as e:
print("reconfigure errors after a read ->", type(e).__name__, e)
reconfigure errors after a read -> UnsupportedOperation It is not possible to set the encoding or newline of stream after the first read
Measured: reconfiguring errors= on the read side raises immediately if even one character has already been read -- the same guard that protects encoding= on reads. The write side has no equivalent check for either parameter.
4. The fix: never reconfigure(errors=...) on a stream you're actively writing
buf2 = io.BytesIO()
text2 = io.TextIOWrapper(buf2, encoding="utf-8", errors="strict")
text2.write("hello ")
text2.write("café") # utf-8 can encode this; no need to relax errors=
text2.flush()
print(io.TextIOWrapper(io.BytesIO(buf2.getvalue()), encoding="utf-8").read())
hello café
Measured: picking an encoding that can actually represent the text you're writing (here, UTF-8 instead of ASCII) avoids the problem entirely. If some input genuinely can't be encoded and dropping it is acceptable, do the lossy conversion explicitly on the string before writing (s.encode('ascii', errors='ignore').decode('ascii')) so the loss is visible at the call site, instead of hiding it inside a stream's mutable state.
Frequently asked questions
Does TextIOWrapper.reconfigure(errors=...) check anything before switching on the write side?
We measured no restriction at all on the write side -- it can be called after any number of writes and takes effect immediately for subsequent writes, including silently dropping characters that errors='ignore' can't encode.
What's different between this and the reconfigure(encoding=...) mid-write bug?
We measured both as unrestricted on the write side, but the consequences differ: switching encoding= mid-write produces a file with two different valid byte sequences concatenated (recoverable if you know the split point); switching to errors='ignore' mid-write destroys data outright -- the dropped characters aren't recoverable from the output at all.
Why does the read side block reconfigure(errors=...) but the write side doesn't?
We measured that reconfigure() raises UnsupportedOperation on the read side as soon as any read() has happened, for both the encoding and errors parameters, because the internal incremental decoder's state can't change mid-stream. The write side has no equivalent guard for either parameter.
How would I detect a file that lost characters this way?
There's no way to detect it from the file alone -- we measured that the dropped characters leave no trace, not even a placeholder. The only reliable fix is avoiding the mid-write reconfigure(errors=...) call in the first place.
Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5's built-in io.TextIOWrapper: wrote text under errors='strict', caught the UnicodeEncodeError it correctly raises, then called reconfigure(errors='ignore') and wrote the same text again. PlainHeaders is our extension; Python's io module is built in.