# ruff UP009: 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: UP009 (tool labels the fix: unknown, this fix reported as safe)
- Oracle: fix-changes-behavior
- Judge (Nemotron 3 Ultra, advisory): real, severity 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.
- Program: minimized from 12 to 12 lines

## Reproducer

```python
# -*- coding: utf-8 -*-
class DebugCM:
    def __enter__(self):
        import inspect
        self.line = inspect.currentframe().f_lineno
        return self
    def __exit__(self, *args):
        pass
    def write(self, s):
        print(s)
with DebugCM() as cm:
    cm.write(f'line {cm.line}')
```

## Commands

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

## Observed

```
stdout before:
line 5
stdout after:
line 4
```

## Diff applied by the fix

```diff
--- orig.py	2026-10-22 13:38:51.597493627 -0400
+++ fixed.py	2026-10-22 13:38:54.663545521 -0400
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 class DebugCM:
     def __enter__(self):
         import inspect
```

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

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.

## Upstream search

Queries: "UP009 fix", "ruff UP009 encoding declaration removal", "ruff UP009 UTF-8 comment deletion behavior", "ruff UP009 file encoding pragma auto-fix"

- related: https://github.com/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><summary>All hits</summary>

- [github, open] unspecified-encoding (PLW1514) still raises in Python 3.15 — https://github.com/astral-sh/ruff/issues/29326
- [github, closed] unused-function-argument not silenced by prefixed underscore if variable name is "次" — https://github.com/astral-sh/ruff/issues/28721
- [github, closed] `per-file-ignores` single-level glob patterns match files in subdirectories — https://github.com/astral-sh/ruff/issues/21425
- [github, closed] UP009 fix changes a file from UTF-8 to a different declared encoding — https://github.com/astral-sh/ruff/issues/14704
- [github, closed] Pack of fuzzer Autofix/Infinite loop errors [2] — https://github.com/astral-sh/ruff/issues/8402
- [github, closed] Autofix errors from extended ecosystem checks — https://github.com/astral-sh/ruff/issues/4523
- [github, closed] Fixed code unformatted — https://github.com/astral-sh/ruff/issues/8619
- [github, closed] Add option to disable preview warnings — https://github.com/astral-sh/ruff/issues/8620
- [github, closed] Implement pyupgrade — https://github.com/astral-sh/ruff/issues/827

</details>
