# 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): real, severity 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.
- Program: minimized from 6 to 5 lines

## Reproducer

```python
flag = False
with open('s.txt', 'w') as f:
    flag and f.write('should not happen')
with open('s.txt', 'r') as f:
    print(repr(f.read()))
```

## Commands

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

## Observed

```
stdout before:
''
stdout after:
'should not happen'
```

## Diff applied by the fix

```diff
--- orig.py	2026-10-22 13:25:04.496627533 -0400
+++ fixed.py	2026-10-22 13:25:07.702680413 -0400
@@ -1,5 +1,5 @@
+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()))
```

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

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.

## Upstream search

Queries: "FURB103 fix", "FURB103 short-circuit logical expression", "FURB103 boolean flag file write", "FURB103 conditional file operation"

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

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