# ruff FURB103: fix changes program behavior

Internal evidence sheet generated by fixhunt. Not an issue text. A human re-verifies, checks the duplicates
below, and writes the upstream report in their own words following the project's AI policy.

- Tool: ruff ruff 0.17.1
- Rule: FURB103 (tool labels the fix: unknown, this fix reported as safe)
- Oracle: fix-changes-behavior
- Judge (Nemotron 3 Ultra, advisory): program-bug, severity low. The program already fails before the fix because the object lacks a `__bytes__` method, causing a TypeError when `f.write(obj)` is called. The fix changes the error message slightly (from `TypeError: a bytes-like object is required` to `TypeError: memoryview: a bytes-like object is required`) but the program was already broken due to a missing `__bytes__` implementation, unrelated to the rewrite.
- Program: minimized from 8 to 5 lines

## Reproducer

```python
class BytesLike:
    def __str__(self): return 'str fall'
obj = BytesLike()
with open('coerce.bin', 'wb') as f:
    f.write(obj)  # works via __bytes__
```

## Commands

```sh
python3 prog.py
ruff check --preview --isolated --select FURB103 --fix prog.py
python3 prog.py
```

## Observed

```
stderr before: TypeError: a bytes-like object is required, not 'BytesLike'
stderr after: TypeError: memoryview: a bytes-like object is required, not 'BytesLike'
```

## Diff applied by the fix

```diff
--- orig.py	2026-10-27 22:11:45.136444401 +0000
+++ fixed.py	2026-10-27 22:11:47.240560588 +0000
@@ -1,5 +1,5 @@
+import pathlib
 class BytesLike:
     def __str__(self): return 'str fall'
 obj = BytesLike()
-with open('coerce.bin', 'wb') as f:
-    f.write(obj)  # works via __bytes__
+pathlib.Path('coerce.bin').write_bytes(obj)  # works via __bytes__
```

## Why the rewrite is not equivalent (model draft, verify before use)

The original `open` call uses `'wb'` mode, which expects a file object that accepts bytes; writing `__str__`'s string result would raise a clear TypeError about needing a bytes‑like object. The replacement calls `pathlib.Path(...).write_bytes`, which internally creates a memoryview from the argument before checking its type, so the same string from `__str__` triggers a TypeError that mentions memoryview, altering the error message and thus the observable behavior. Since the exception text differs, the transformation is not semantics‑preserving.

## Upstream search

Queries: "FURB103 fix", "FURB103 file write binary", "ruff rule FURB103 pathlib Path write_bytes", "FURB103 coercion to bytes"

- duplicate: https://github.com/astral-sh/ruff/issues/26920
  Report 26920 describes the same defect: the FURB103 fix replaces open/write with Path.write_bytes, changing behavior because open truncates the file before write fails on a non-bytes-like object.
- related: https://github.com/astral-sh/ruff/issues/26922
  Report 26922 covers a similar safety issue for FURB101 (read-whole-file) and mentions FURB103, but the specific construct and error differ.
- related: https://github.com/astral-sh/ruff/issues/20785
  Report 20785 states the FURB103 fix breaks code, but the example involves json.dumps and a different failure mode.

<details><summary>All hits</summary>

- [github, open] FURB103 safe fix drops expressions surrounding write() — https://github.com/astral-sh/ruff/issues/29452
- [github, open] FURB103 fix skips file truncation when the data to write has the wrong type — https://github.com/astral-sh/ruff/issues/26920
- [github, open] `FURB101` and `FURB103` apply safe fixes that cause runtime errors — https://github.com/astral-sh/ruff/issues/26922
- [github, closed] FURB101 and FURB103 shouldn’t apply when the I/O variable is used later — https://github.com/astral-sh/ruff/issues/21483
- [github, closed] [FURB] Path write — https://github.com/astral-sh/ruff/issues/18409
- [github, closed] `FURB103` fixes breaks code — https://github.com/astral-sh/ruff/issues/20785

</details>
