ruff FURB103: fix changes program behavior
Hypothesis Nemotron 3 Super predicted to fire fired 1×
Incorrectly handles non-string/write-incompatible data types; write_text expects str, write_bytes expects bytes, but the original .write() might have relied on coercion or custom __write__.
Program
Output
(no stdout) TypeError: write() argument must be str, not Num
(no stdout) TypeError: data must be str, not Num
- fired
- 1 before · 0 after
- runs
- each version twice, same file name, no network
- minimizer
- 21 oracle evaluations
Judge Nemotron 3 Ultra advisory program-bug · 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.
Explanation Nemotron 3 Super unverified
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.
Reproduce
- file
prog.py(the before program)- minimized program
- r2-p007.py
- evidence sheet
- r2-p007.md
- regression fixture
- r2-p007.py the minimized program with a header stating the observed change, for the tool's test suite
python3 prog.py ruff check --preview --isolated --select FURB103 --fix prog.py python3 prog.py
Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra
FURB103 fix · FURB103 file write text conversion · ruff FURB103 Path write_text usage · FURB103 open write to pathlib migration
- duplicate 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 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 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 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
- program
- Hazard 7: Non-string write with coercion
- hazard
- Incorrectly handles non-string/write-incompatible data types; write_text expects str, write_bytes expects bytes, but the original .write() might have relied on coercion or custom __write__.
- oracle
stderr before: TypeError: write() argument must be str, not Num stderr after: TypeError: data must be str, not Num- run
- 2026-10-27T22-10-17-844Z-aav5-ruff-FURB103
generated program (8 lines) and the fix's diff
--- orig.py 2026-10-27 22:10:51.230467835 +0000 +++ fixed.py 2026-10-27 22:10:55.883724773 +0000 @@ -1,8 +1,8 @@ +import pathlib class Num: def __str__(self): return str(self.value) 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) with open('num.txt') as f: print(f.read())
rule documentation given to the models
# write-whole-file (FURB103)
Derived from the **refurb** linter.
Fix is sometimes available.
This rule is in preview and is not stable. The `--preview` flag is required for use.
## What it does
Checks for uses of `open` and `write` that can be replaced by `pathlib`
methods, like `Path.write_text` and `Path.write_bytes`.
## Why is this bad?
When writing a single string to a file, it's simpler and more concise
to use `pathlib` methods like `Path.write_text` and `Path.write_bytes`
instead of `open` and `write` calls via `with` statements.
## Example
```python
with open("file.txt", "w") as f:
f.write("some text")
```
Use instead:
```python
from pathlib import Path
Path("file.txt").write_text("some text")
```
## Fix Safety
This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
## References
- [Python documentation: `Path.write_bytes`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.write_bytes)
- [Python documentation: `Path.write_text`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.write_text)