fixhunt

ruff UP009: fix changes program behavior

fix-changes-behaviorlabelled safereal · lowrelated
Findings / 2026-10-17T21-13-56-323Z-xzoc-ruff-UP009 / r2-p006ruff 0.17.112 → 12 lines2026-10-17 21:13 UTC

Hypothesis Nemotron 3 Super predicted to fire fired 1×

context managers that are not files, objects with write() but no close()

Program

before
1# -*- coding: utf-8 -*-
2class DebugCM:
3 def __enter__(self):
4 import inspect
5 self.line = inspect.currentframe().f_lineno
6 return self
7 def __exit__(self, *args):
8 pass
9 def write(self, s):
10 print(s)
11with DebugCM() as cm:
12 cm.write(f'line {cm.line}')
after
1class DebugCM:
2 def __enter__(self):
3 import inspect
4 self.line = inspect.currentframe().f_lineno
5 return self
6 def __exit__(self, *args):
7 pass
8 def write(self, s):
9 print(s)
10with DebugCM() as cm:
11 cm.write(f'line {cm.line}')

Output

stdout beforeexit 0
line 5
stdout afterexit 0
line 4
fired
1 before · 0 after
runs
each version twice, same file name, no network

Judge Nemotron 3 Ultra advisory real · low

Removing the encoding declaration shifts all subsequent line numbers up by one, which changes the value returned by inspect.currentframe().f_lineno. The rule documentation presents the rewrite as semantically equivalent, but the program observes its own source location, so the behavior changes.

Explanation Nemotron 3 Super unverified

Removing the encoding declaration changes how Python interprets source file bytes; while ASCII‑compatible here, the specification treats its absence as a different decoding step, which can affect line numbering in error reporting and debugging tools. The `inspect.currentframe().f_lineno` value depends on the exact line count as seen by the interpreter after decoding, so dropping the declaration shifts the perceived line number by one, causing the printed value to change from 5 to 4. This alters observable output, violating semantic equivalence.

Reproduce

file
prog.py (the before program)
minimized program
r2-p006.py
evidence sheet
r2-p006.md
regression fixture
r2-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 UP009 --fix prog.py
python3 prog.py

Upstream search GitHub issues Tavily Nemotron 3 Super + Ultra

UP009 fix · ruff UP009 encoding declaration removal · ruff UP009 UTF-8 comment deletion behavior · ruff UP009 file encoding pragma auto-fix

  • related astral-sh/ruff/issues/14704 Same UP009 rule but different defect: that report involves a non-UTF-8 declaration after the UTF-8 one, while this finding removes a lone UTF-8 declaration and shifts line numbers.

Details

program
Non-file context manager relying on line numbers for ID
hazard
context managers that are not files, objects with write() but no close()
oracle
stdout before: line 5 stdout after: line 4
run
2026-10-17T21-13-56-323Z-xzoc-ruff-UP009
generated program (12 lines) and the fix's diff
1# -*- coding: utf-8 -*-
2class DebugCM:
3 def __enter__(self):
4 import inspect
5 self.line = inspect.currentframe().f_lineno
6 return self
7 def __exit__(self, *args):
8 pass
9 def write(self, s):
10 print(s)
11with DebugCM() as cm:
12 cm.write(f'line {cm.line}')
--- orig.py	2026-10-17 17:14:13.288784170 -0400
+++ fixed.py	2026-10-17 17:14:13.502765787 -0400
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 class DebugCM:
     def __enter__(self):
         import inspect
rule documentation given to the models
# utf8-encoding-declaration (UP009)

Derived from the **pyupgrade** linter.

Fix is always available.

## What it does
Checks for unnecessary UTF-8 encoding declarations.

## Why is this bad?
[PEP 3120] makes UTF-8 the default encoding, so a UTF-8 encoding
declaration is unnecessary.

## Example
```python
# -*- coding: utf-8 -*-
print("Hello, world!")
```

Use instead:
```python
print("Hello, world!")
```

[PEP 3120]: https://peps.python.org/pep-3120/