# ruff TC006: 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: TC006 (tool labels the fix: unknown, this fix reported as safe)
- Oracle: fix-changes-behavior
- Judge (Nemotron 3 Ultra, advisory): real, severity high. The rule quotes the first argument of typing.cast() to enforce stylistic consistency, but the program uses a forward reference (T is assigned later). Quoting 'T' changes the runtime semantics: before the fix, the UnboundLocalError occurred because T was referenced before assignment; after the fix, the quoted string 'T' is passed, so the cast succeeds but x is undefined, causing a NameError. The documentation's safety condition does not cover forward references, making this a real bug in the fix.
- Program: minimized from 20 to 10 lines

## Reproducer

```python
from typing import cast, List
def main():
    y = cast(T, x)  # Should be cast(str, x)
    T = list  # Now T is the lambda
    try:
        print('z:', z)
    except Exception as e:
        print('Error after redefinition:', type(e).__name__, str(e))
if __name__ == '__main__':
    main()
```

## Commands

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

## Observed

```
stderr before: UnboundLocalError: cannot access local variable 'T' where it is not associated with a value
stderr after: NameError: name 'x' is not defined
```

## Diff applied by the fix

```diff
--- orig.py	2026-10-22 13:30:08.893688223 -0400
+++ fixed.py	2026-10-22 13:30:10.964722882 -0400
@@ -1,6 +1,6 @@
 from typing import cast, List
 def main():
-    y = cast(T, x)  # Should be cast(str, x)
+    y = cast('T', x)  # Should be cast(str, x)
     T = list  # Now T is the lambda
     try:
         print('z:', z)
```

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

The original uses `cast(T, x)` where `T` refers to the later assignment `T = list`, so `T` resolves to the built-in `list`. After rewriting, `'T'` becomes a string literal passed as the first argument to `cast`, changing its meaning entirely—now interpreted as a type annotation for `str`? Actually `cast('T', x)` treats `'T'` as a forward reference string, but since no such type exists in scope, it fails during type checking or runtime depending on context; however here the immediate difference is that `x` remains undefined in both, but the error shifts because the first argument’s evaluation changes: originally `T` is looked up (failing later due to `x`), while now `'T'` is a constant, so the lookup of `x` happens first and fails earlier with `NameError`. Thus the fix alters runtime behavior by changing what expression is evaluated for the type argument, making it not semantics-preserving.

## Upstream search

Queries: "TC006 fix", "ruff TC006 cast undefined variable", "ruff TC006 cast string literal conversion", "ruff TC006 cast forward reference handling"

- related: https://github.com/astral-sh/ruff/issues/19596
  Both involve TC006 autofix on cast, but existing report concerns EM101 interaction and string literal type, while new finding concerns forward reference redefinition.
- related: https://github.com/astral-sh/ruff/issues/14676
  Both involve TC006 adding quotes, but existing report is about over-broad quoting in general, while new finding is specifically about quoting a forward reference that gets redefined.
- related: https://github.com/astral-sh/ruff/issues/20237
  Both involve TC006 and forward references, but existing report is about false positive with from __future__ import annotations, while new finding is about quoting a forward reference that is later redefined to a different value.

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

- [github, open] TC006 fix creates quoted type expressions with escape sequences — https://github.com/astral-sh/ruff/issues/22131
- [github, open] [flake8-type-checking] Missing rules, autofix strategies, overlapping rules and more — https://github.com/astral-sh/ruff/issues/13208
- [github, closed] UP045 doesn't trigger inside of `cast` — https://github.com/astral-sh/ruff/issues/20096
- [github, closed] EM101 false positive — https://github.com/astral-sh/ruff/issues/19596
- [github, closed] Infinite loop when fixing file with `cast` and format strings — https://github.com/astral-sh/ruff/issues/16037
- [github, closed] unnecessary-literal-union (PYI030) can remove comments without being marked as unsafe — https://github.com/astral-sh/ruff/issues/16317
- [github, closed] `TC006` in `0.8.1` is too broad — https://github.com/astral-sh/ruff/issues/14676
- [github, closed] Implement flake8-type-checking — https://github.com/astral-sh/ruff/issues/1785
- [tavily] Reharmonize `TCH*` rules with flake8-type-checking · Issue #9573 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/9573
- [tavily] Checking file with rule TC006 cause panic · Issue #14554 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/14554
- [tavily] TC006 (runtime-cast-value) shouldn't trigger with `annotations` · Issue #20237 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/20237
- [tavily] Variable named "time" incorrectly marked as "Undefined name" (F821) in Jupyter notebook · Issue #376 · astral-sh/ruff-vscode · GitHub — https://github.com/astral-sh/ruff-vscode/issues/376
- [tavily] F841 "Undefined name" false positive in exception-nested-closure · Issue #6878 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/6878
- [tavily] F821 Undefined name for variables loaded with notebook %store magic · Issue #25807 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/25807
- [tavily] False undefined name (F821) and remove quotes from type annotation (UP037) when string is expected · Issue #20716 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/20716
- [tavily] Format breaks f-string with literal strings inside (versions 0.3.0 and 0.3.1) · Issue #10273 · astral-sh/ruff · GitHub — https://github.com/astral-sh/ruff/issues/10273

</details>
