fixhunt

ruff FURB103: fix changes program behavior

fix-changes-behaviorlabelled safereal · highduplicate
Findings / 2026-10-17T19-38-23-545Z-dfhv-ruff-FURB103 / r1-p006ruff 0.17.16 → 5 lines2026-10-17 19:38 UTC

Hypothesis Nemotron 3 Super predicted to fire fired 1×

If the write call is guarded by a short-circuiting expression (e.g., cond and f.write(s)), the fix hoists it unconditionally.

Program

before
1flag = False
2with open('s.txt', 'w') as f:
3 flag and f.write('should not happen')
4with open('s.txt', 'r') as f:
5 print(repr(f.read()))
after
1import pathlib
2flag = False
3pathlib.Path('s.txt').write_text('should not happen')
4with open('s.txt', 'r') as f:
5 print(repr(f.read()))

Output

stdout beforeexit 0
''
stdout afterexit 0
'should not happen'
fired
1 before · 0 after
runs
each version twice, same file name, no network

Judge Nemotron 3 Ultra advisory real · high

The rule rewrites a guarded write (`flag and f.write(...)`) inside a `with open` block to an unguarded `Path.write_text(...)`, removing the conditional that prevented the write when `flag` is False. The documentation presents the rewrite as equivalent for writing a single string, but does not account for conditional writes, so the fix changes program behavior silently.

Explanation Nemotron 3 Super unverified

The original condition `flag and f.write(...)` short‑circuits because `flag` is `False`, so `f.write` is never called; the file remains empty. The replacement unconditionally writes `'should not happen'` via `pathlib.Path.write_text`, creating content that did not exist before, thus changing the program’s output from an empty string to `'should not happen'`. This alters observable behavior, violating semantic equivalence.

Reproduce

file
prog.py (the before program)
minimized program
r1-p006.py
evidence sheet
r1-p006.md
regression fixture
r1-p006.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 short-circuit logical expression · FURB103 boolean flag file write · FURB103 conditional file operation

  • duplicate astral-sh/ruff/issues/29452 same rule FURB103 dropping the surrounding expression (flag and ...) when rewriting a conditional write to Path.write_text
  • related astral-sh/ruff/issues/26920 same rule FURB103 but different defect: truncation semantics vs. dropped conditional expression
  • related astral-sh/ruff/issues/26922 same rule family FURB101/FURB103 but different defect: runtime errors from safe fixes vs. dropped conditional expression
  • related astral-sh/ruff/issues/21483 same rule FURB103 but different defect: variable used later vs. dropped conditional expression
  • related astral-sh/ruff/issues/18409 same rule FURB103 but different context: encoding/path.open suggestion vs. dropped conditional expression
  • related astral-sh/ruff/issues/20785 same rule FURB103 but different defect: json.dumps serialization breakage vs. dropped conditional expression

Details

program
Short-circuiting expression guards write
hazard
If the write call is guarded by a short-circuiting expression (e.g., cond and f.write(s)), the fix hoists it unconditionally.
oracle
stdout before: '' stdout after: 'should not happen'
run
2026-10-17T19-38-23-545Z-dfhv-ruff-FURB103
generated program (6 lines) and the fix's diff
1flag = False
2with open('s.txt', 'w') as f:
3 flag and f.write('should not happen')
4with open('s.txt', 'r') as f:
5 print(repr(f.read()))
6import os; os.remove('s.txt')
--- orig.py	2026-10-17 15:38:33.452462839 -0400
+++ fixed.py	2026-10-17 15:38:33.605460950 -0400
@@ -1,6 +1,6 @@
+import pathlib
 flag = False
-with open('s.txt', 'w') as f:
-    flag and f.write('should not happen')
+pathlib.Path('s.txt').write_text('should not happen')
 with open('s.txt', 'r') as f:
     print(repr(f.read()))
 import os; os.remove('s.txt')
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)