# ruff FURB129: 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: FURB129 (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 `for line in fp.readlines():` to `for line in fp:`, which changes the file position semantics. In the original code, `fp.readlines()` inside the loop reads from the current position (which is at the start of the next line after the `for` loop's implicit `readline()`), returning the remaining lines. After the fix, the `for` loop consumes lines one by one via iteration, so the internal `fp.readlines()` sees fewer remaining lines each iteration, producing different output. The documentation presents the rewrite as equivalent for simple iteration but does not account for nested reads on the same file object.
- Program: minimized from 13 to 10 lines

## Reproducer

```python
with open('tmp.txt', 'w') as f:
    f.write('apple\nbanana\ncherry')
with open('tmp.txt') as fp:
    trace = []
    for line in fp.readlines():  # Rule triggers here
        try:
            trace.append(f'{len(fp.readlines())}:{line.strip()}')
        except AttributeError as e:
            trace.append(f'ERR:{type(e).__name__}')
print(','.join(trace))
```

## Commands

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

## Observed

```
stdout before:
0:apple,0:banana,0:cherry
stdout after:
2:apple
```

## Diff applied by the fix

```diff
--- orig.py	2026-10-22 13:44:17.594345801 -0400
+++ fixed.py	2026-10-22 13:44:19.523354486 -0400
@@ -2,7 +2,7 @@
     f.write('apple\nbanana\ncherry')
 with open('tmp.txt') as fp:
     trace = []
-    for line in fp.readlines():  # Rule triggers here
+    for line in fp:  # Rule triggers here
         try:
             trace.append(f'{len(fp.readlines())}:{line.strip()}')
         except AttributeError as e:
```

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

The original loop iterates over lines from `fp.readlines()`, which returns all remaining lines each time it’s called; inside the loop, `fp.readlines()` consumes the rest of the file on the first iteration, returning two more lines (so length 2), then exhausts the file, making subsequent calls return empty lists (length 0). After fixing to iterate directly over `fp`, the loop yields one line per iteration, but `fp.readlines()` now reads from the current file position onward: on the first iteration it reads the remaining two lines (length 2), advancing the file pointer to end‑of‑file, so later iterations see zero remaining lines. Hence only the first iteration produces output (`2:apple`), changing behavior.

## Upstream search

Queries: "FURB129 fix", "FURB129 readlines iteration variable reuse", "FURB129 file iterator exhaustion bug", "FURB129 fp.readlines() inside loop"

- related: https://github.com/astral-sh/ruff/issues/18843
  Same FURB129 rule but different defect: comment deletion during fix rather than behavior change from readlines() consumption
- related: https://github.com/astral-sh/ruff/issues/17683
  Same FURB129 rule but different defect: missing whitespace around tokens causing syntax error rather than semantic behavior change
- related: https://github.com/astral-sh/ruff/issues/18231
  Same FURB129 rule but different defect: parenthesized variable causing syntax error rather than readlines() exhaustion changing loop semantics

<details><summary>All hits</summary>

- [github, closed] unused-function-argument not silenced by prefixed underscore if variable name is "次" — https://github.com/astral-sh/ruff/issues/28721
- [github, closed] [`refurb`] `FURB129` fix can delete comments — https://github.com/astral-sh/ruff/issues/18843
- [github, closed] FURB129 fix should add spaces between tokens — https://github.com/astral-sh/ruff/issues/17683
- [github, closed] FURB129 fix fails when the variable is parenthesized — https://github.com/astral-sh/ruff/issues/18231
- [github, closed] Pack of fuzzer Autofix/Infinite loop errors [2] — https://github.com/astral-sh/ruff/issues/8402

</details>
