fixhunt

ruff FURB129: fix changes program behavior

fix-changes-behaviorlabelled safereal · highrelated
Findings / 2026-10-17T21-30-17-728Z-bxl4-ruff-FURB129 / r2-p002ruff 0.17.112 → 8 lines2026-10-17 21:30 UTC

Hypothesis Nemotron 3 Super predicted to fire fired 1×

If the file object is used elsewhere in the loop body (e.g., `fp.seek(0)`), switching to direct iteration may interfere with internal buffering or iteration state, causing skipped lines or infinite lo

Program

before
1def main():
2 with open('tmp.txt', 'w') as f:
3 f.write('0\n1\n2')
4 with open('tmp.txt') as fp:
5 for line in fp.readlines():
6 fp.seek(0)
7if __name__ == '__main__':
8 main()
after
1def main():
2 with open('tmp.txt', 'w') as f:
3 f.write('0\n1\n2')
4 with open('tmp.txt') as fp:
5 for line in fp:
6 fp.seek(0)
7if __name__ == '__main__':
8 main()

Output

stdout beforeexit 0
(no stdout)
stdout aftertimed out
(no stdout)
fired
1 before · 0 after
runs
each version twice, same file name, no network

Judge Nemotron 3 Ultra advisory real · high

The fix replaces `fp.readlines()` with `fp`, changing iteration from a pre-read list to a live file iterator. The loop body calls `fp.seek(0)`, which resets the file position on each iteration. With `readlines()`, this safely re-reads the same lines from the start; with the direct iterator, it creates an infinite loop because the iterator never exhausts, causing the observed timeout.

Explanation Nemotron 3 Super unverified

The original reads all lines into a list via readlines() before iterating; during iteration, each seek(0) resets the file pointer to the start, but since the loop operates on the pre-loaded list, it iterates exactly three times (for '0\n', '1\n', '2'), then exits. The rewritten version iterates directly over the file object fp, which yields lines lazily. Each seek(0) inside the loop resets fp to the beginning before the next line is read, causing the iterator to repeatedly yield the first line '0\n' indefinitely, resulting in an infinite loop and timeout. Thus, the change alters control flow from finite to non-terminating.

Reproduce

file
prog.py (the before program)
minimized program
r2-p002.py
evidence sheet
r2-p002.md
regression fixture
r2-p002.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 · fp seek zero loop · file iterator replacement

  • related astral-sh/ruff/issues/18843 Same FURB129 rule but defect is comment deletion during rewrite, not an infinite loop caused by seek inside the loop.
  • related astral-sh/ruff/issues/17683 Same FURB129 rule but defect is missing whitespace after rewrite, not an infinite loop caused by seek inside the loop.
  • related astral-sh/ruff/issues/18231 Same FURB129 rule but defect is syntax error with parenthesized file variable, not an infinite loop caused by seek inside the loop.
  • related astral-sh/ruff/issues/8402 Meta-issue collecting autofix infinite-loop errors; the new finding is another instance of that class but not the same specific construct.

Details

program
File Object Used Elsewhere in Loop Body
hazard
If the file object is used elsewhere in the loop body (e.g., `fp.seek(0)`), switching to direct iteration may interfere with internal buffering or iteration state, causing skipped lines or infinite lo
oracle
exit 0 → timeout
run
2026-10-17T21-30-17-728Z-bxl4-ruff-FURB129
generated program (12 lines) and the fix's diff
1def main():
2 with open('tmp.txt', 'w') as f:
3 f.write('0\n1\n2')
4 with open('tmp.txt') as fp:
5 for line in fp.readlines():
6 if line.strip() == '1':
7 fp.seek(0)
8 print(line.strip())
9 import os
10 os.remove('tmp.txt')
11if __name__ == '__main__':
12 main()
--- orig.py	2026-10-17 17:30:44.966729680 -0400
+++ fixed.py	2026-10-17 17:30:45.132727007 -0400
@@ -2,7 +2,7 @@
     with open('tmp.txt', 'w') as f:
         f.write('0\n1\n2')
     with open('tmp.txt') as fp:
-        for line in fp.readlines():
+        for line in fp:
             if line.strip() == '1':
                 fp.seek(0)
             print(line.strip())
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)