ruff FURB129: fix changes program behavior
Hypothesis Nemotron 3 Super predicted to fire fired 1×
If the code relies on readlines returning a list (e.g., for later indexing or len() inside the loop), the fix changes the type from list to _io.TextIOWrapper, breaking such operations.
Program
Output
0:apple,0:banana,0:cherry
2:apple
- fired
- 1 before · 0 after
- runs
- each version twice, same file name, no network
Judge Nemotron 3 Ultra advisory real · 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.
Explanation Nemotron 3 Super unverified
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.
Reproduce
- file
prog.py(the before program)- minimized program
- r1-p003.py
- evidence sheet
- r1-p003.md
- regression fixture
- r1-p003.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 FURB129 --fix prog.py python3 prog.py
Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra
FURB129 fix · FURB129 readlines iteration variable reuse · FURB129 file iterator exhaustion bug · FURB129 fp.readlines() inside loop
- related astral-sh/ruff/issues/18843 Same FURB129 rule but different defect: comment deletion during fix rather than behavior change from readlines() consumption
- related astral-sh/ruff/issues/17683 Same FURB129 rule but different defect: missing whitespace around tokens causing syntax error rather than semantic behavior change
- related astral-sh/ruff/issues/18231 Same FURB129 rule but different defect: parenthesized variable causing syntax error rather than readlines() exhaustion changing loop semantics
Details
- program
- Reliance on readlines Returning a List for Indexing Inside Loop
- hazard
- If the code relies on readlines returning a list (e.g., for later indexing or len() inside the loop), the fix changes the type from list to _io.TextIOWrapper, breaking such operations.
- oracle
stdout before: 0:apple,0:banana,0:cherry stdout after: 2:apple- run
- 2026-10-22T17-16-41-266Z-868l-ruff-FURB129
generated program (13 lines) and the fix's diff
--- orig.py 2026-10-22 13:16:52.516694685 -0400 +++ fixed.py 2026-10-22 13:16:56.477756573 -0400 @@ -3,7 +3,7 @@ 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:
rule documentation given to the models
# readlines-in-for (FURB129)
Derived from the **refurb** linter.
Fix is always available.
## What it does
Checks for uses of `readlines()` when iterating over a file line-by-line.
## Why is this bad?
Rather than iterating over all lines in a file by calling `readlines()`,
it's more convenient and performant to iterate over the file object
directly.
## Example
```python
with open("file.txt") as fp:
for line in fp.readlines():
...
```
Use instead:
```python
with open("file.txt") as fp:
for line in fp:
...
```
## Fix safety
This rule's fix is marked as unsafe if there's comments in the
`readlines()` call, as comments may be removed.
For example, the fix would be marked as unsafe in the following case:
```python
with open("file.txt") as fp:
for line in ( # comment
fp.readlines() # comment
):
...
```
## References
- [Python documentation: `io.IOBase.readlines`](https://docs.python.org/3/library/io.html#io.IOBase.readlines)