fixhunt

ruff FURB103: fix changes program behavior

fix-changes-behaviorlabelled safeprogram-bug · lowno match
Findings / 2026-10-27T22-10-17-844Z-aav5-ruff-FURB103 / r1-p009ruff 0.17.19 → 7 lines2026-10-27 22:10 UTC

Hypothesis Nemotron 3 Super predicted to fire fired 1×

Ignores custom openers or file descriptors passed via opener=, which Path.write_text does not support.

Program

before
1import os
2fd = os.open('custom.txt', os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
3with open(fd, 'w') as f:
4 f.write('via fd')
5os.close(fd)
6with open('custom.txt') as f:
7 print(repr(f.read()))
after
1import os
2import pathlib
3fd = os.open('custom.txt', os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
4pathlib.Path(fd).write_text('via fd')
5os.close(fd)
6with open('custom.txt') as f:
7 print(repr(f.read()))

Output

stdout beforeexit 1
(no stdout)
OSError: [Errno 9] Bad file descriptor
stdout afterexit 1
(no stdout)
TypeError: argument should be a str or an os.PathLike object where __fspath__ returns a str, not 'int'
fired
1 before · 0 after
runs
each version twice, same file name, no network
minimizer
29 oracle evaluations

Judge Nemotron 3 Ultra advisory program-bug · low

The original program already fails with OSError: [Errno 9] Bad file descriptor because os.open returns an integer file descriptor, but the built-in open() function expects a path string or path-like object, not a raw file descriptor. The fix changes the error to a TypeError from pathlib.Path, but the program was already broken before the rewrite due to incorrect usage of os.open with open().

Explanation Nemotron 3 Super unverified

The original code opens a file via `os.open`, obtaining an integer file descriptor (`fd`), then uses it directly in `open(fd, 'w')` to create a text stream for writing—this is valid because Python’s built-in `open()` accepts integer file descriptors. The rewrite replaces this with `pathlib.Path(fd).write_text(...)`, but `Path` expects a string or path-like object (e.g., filename), not an integer file descriptor. Passing an `int` to `Path()` raises a `TypeError` because integers do not implement `__fspath__`. Thus, the rewrite fails at runtime where the original succeeded, altering behavior from successful file write to immediate exception. This violates semantic preservation: the fix changes what the program does, not just how it does it. The label “safe” is incorrect—the transformation assumes `fd` is a path, but it is a descriptor, making the rewrite invalid for this use case. No speculation about the tool is needed; the error arises directly from mismatched types.

Reproduce

file
prog.py (the before program)
minimized program
r1-p009.py
evidence sheet
r1-p009.md
regression fixture
r1-p009.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 os.open file descriptor usage · ruff FURB103 Path.write_text integer argument · FURB103 replace os.open with pathlib

no duplicate or related report 6 hits triaged

Details

program
FURB103 Hazard 9: Custom Opener Ignored
hazard
Ignores custom openers or file descriptors passed via opener=, which Path.write_text does not support.
oracle
stderr before: OSError: [Errno 9] Bad file descriptor stderr after: TypeError: argument should be a str or an os.PathLike object where __fspath__ returns a str, not 'int'
run
2026-10-27T22-10-17-844Z-aav5-ruff-FURB103
generated program (9 lines) and the fix's diff
1import os
2# Use fd 3 via dup
3fd = os.open('custom.txt', os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
4os.set_inheritable(fd, True)
5with open(fd, 'w') as f:
6 f.write('via fd')
7os.close(fd)
8with open('custom.txt') as f:
9 print(repr(f.read()))
--- orig.py	2026-10-27 22:10:36.039629052 +0000
+++ fixed.py	2026-10-27 22:10:39.566823810 +0000
@@ -1,9 +1,9 @@
 import os
+import pathlib
 # Use fd 3 via dup
 fd = os.open('custom.txt', os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
 os.set_inheritable(fd, True)
-with open(fd, 'w') as f:
-    f.write('via fd')
+pathlib.Path(fd).write_text('via fd')
 os.close(fd)
 with open('custom.txt') as f:
     print(repr(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)