# 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 with a TypeError because f.write() requires a string argument but receives a Num object. The fix changes the error message from 'write() argument must be str' to 'data must be str' but both are TypeErrors from the same root cause: passing a non-string to a write operation. The behavioral difference is solely due to the pre-existing failure, not the rewrite itself.
- Program: minimized from 8 to 5 lines

## Reproducer

```python
class Num:
    def __init__(self, v): self.value = v
n = Num(42)
with open('num.txt', 'w') as f:
    f.write(n)
```

## Commands

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

## Observed

```
stderr before: TypeError: write() argument must be str, not Num
stderr after: TypeError: data must be str, not Num
```

## Diff applied by the fix

```diff
--- orig.py	2026-10-27 22:14:18.332904190 +0000
+++ fixed.py	2026-10-27 22:14:20.417019285 +0000
@@ -1,5 +1,5 @@
+import pathlib
 class Num:
     def __init__(self, v): self.value = v
 n = Num(42)
-with open('num.txt', 'w') as f:
-    f.write(n)
+pathlib.Path('num.txt').write_text(n)
```

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

The original code attempts to call `f.write(n)` where `n` is a `Num` instance; Python raises a `TypeError` because `write()` expects a string or bytes object. The rewrite replaces this with `pathlib.Path(...).write_text(n)`, which also requires its argument to be a string (or bytes) but internally performs a similar type check before writing. Both versions fail with a `TypeError` due to passing a non-string `Num` object, so the observable behavior—raising a type error—is unchanged. The fix does not alter semantics; it merely rewrites the call while preserving the same type constraint violation. Thus, the rewrite is semantics‑preserving despite the tool labeling it unsafe.

## Upstream search

Queries: "FURB103 fix", "FURB103 file write text conversion", "ruff FURB103 Path write_text usage", "FURB103 open write to pathlib migration"

- duplicate: https://github.com/astral-sh/ruff/issues/26920
  Report 26920 describes the same defect: the FURB103 fix replaces open/write with Path.write_text, changing behavior because open truncates the file before write fails on a non-str argument.
- related: https://github.com/astral-sh/ruff/issues/26922
  Report 26922 is about safe fixes causing runtime errors for both FURB101 and FURB103, a broader class of the same rule defect.
- related: https://github.com/astral-sh/ruff/issues/18409
  Report 18409 is about the FURB103 fix suggesting Path.write_text in general, not the specific behavior change on type error.
- related: https://github.com/astral-sh/ruff/issues/20785
  Report 20785 shows FURB103 fix breaking code with json.dumps bytes, a different construct but same rule and defect class.

<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>
