python/cpython · error · OSError

write() returned incorrect number of bytes

Error message

write() returned incorrect number of bytes

What it means

Raised by BufferedWriter._flush_unlocked when the raw stream's write() returns an out-of-range byte count — more than the supplied buffer length, or a negative number. Such a return value breaks the writer's bookkeeping (it does 'del self._write_buf[:n]'), so it is treated as a broken raw implementation and surfaced as OSError('write() returned incorrect number of bytes').

Source

Thrown at Lib/_pyio.py:1321

    def flush(self):
        with self._write_lock:
            self._flush_unlocked()

    def _flush_unlocked(self):
        if self.closed:
            raise ValueError("flush on closed file")
        while self._write_buf:
            try:
                n = self.raw.write(self._write_buf)
            except BlockingIOError:
                raise RuntimeError("self.raw should implement RawIOBase: it "
                                   "should not raise BlockingIOError")
            if n is None:
                raise BlockingIOError(
                    errno.EAGAIN,
                    "write could not complete without blocking", 0)
            if n > len(self._write_buf) or n < 0:
                raise OSError("write() returned incorrect number of bytes")
            del self._write_buf[:n]

    def tell(self):
        return _BufferedIOMixin.tell(self) + len(self._write_buf)

    def seek(self, pos, whence=0):
        if whence not in valid_seek_flags:
            raise ValueError("invalid whence value")
        with self._write_lock:
            self._flush_unlocked()
            return _BufferedIOMixin.seek(self, pos, whence)

    def close(self):
        with self._write_lock:
            if self.raw is None or self.closed:
                return
        # We have to release the lock and call self.flush() (which will
        # probably just re-take the lock) in case flush has been overridden in

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Make the custom raw write() return exactly the number of bytes actually written, in [0, len(b)].
  2. For would-block, return None (RawIOBase protocol) rather than -1.
  3. Add a unit test asserting 0 <= raw.write(b) <= len(b) for all paths.

Example fix

# before
class MyRaw(io.RawIOBase):
    def write(self, b):
        self.queue.append(b)
        return len(self.queue)  # wrong: total queued, can exceed len(b)

# after
class MyRaw(io.RawIOBase):
    def write(self, b):
        self.queue.append(b)
        return len(b)  # exactly what was accepted
Defensive patterns

Strategy: type-guard

Validate before calling

n = raw.write(chunk)
assert 0 <= n <= len(chunk), f"raw.write returned {n} for {len(chunk)} bytes"
del chunk[:n]

Type guard

def sane_write_count(raw, probe: bytes) -> bool:
    n = raw.write(probe)
    return n is None or 0 <= n <= len(probe)

Prevention

When it happens

Trigger: Custom raw classes that return len(data) unconditionally (even on short writes), return the total cached length instead of the written count, or return -1 error sentinels; rarely, wrapper math bugs computing n from multiple writes.

Common situations: Buggy hand-written RawIOBase subclasses; adapters over C libraries whose write returns bytes-consumed semantics that include buffered-elsewhere bytes; mocks/fakes in tests returning wrong counts.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/f93495ef461f1dd6. Report an issue: GitHub.