fixhunt

ruff UP024

labelled unknown
Runs / 2026-10-17T21-18-37-674Z-2vyh-ruff-UP024ruff 0.17.12026-10-17 21:18 UTC8 programs × 2 round(s)16 programs, 10 fired, 10 fixed, 0 findings, 0 rejectedmodel cost $0.0052

Analysis Nemotron 3 Super

Flags usage of exception classes that are aliases of OSError (e.g., IOError, EnvironmentError, WindowsError) and recommends replacing them with OSError.

Fix: Replaces any occurrence of an OSError-alias exception name (such as IOError, EnvironmentError, WindowsError) with OSError in raise statements, except clauses, isinstance/issubclass checks, and similar contexts, preserving all surrounding syntax and structure.

11 hazards
  • Rewriting `except IOError:` to `except OSError:` changes the set of caught exceptions if other OSError subclasses (like FileNotFoundError) were previously excluded by a broader except clause above, altering control flow.
  • In `except (IOError, ValueError):`, replacing IOError with OSError may cause ValueError to be unreachable if OSError is checked first, due to exception matching order changing subclass precedence.
  • If `IOError` is imported via `from errno import IOError` (though incorrect, hypothetically), rewriting to `OSError` would break the name resolution since `OSError` is not defined in that module's namespace.
  • In a context like `except IOError as e:`, if the variable `e` is later used to access attributes specific to the IOError alias (none exist, but hypothetically), the rewrite assumes identical interface, which while true now, relies on the alias preserving all behavior.
  • When `WindowsError` is used in `raise WindowsError`, rewriting to `raise OSError` loses the platform-specific signal that the error originated from Windows, though the exception instance behaves identically.
  • In `issubclass(SomeErr, EnvironmentError)`, replacing EnvironmentError with OSError may return False if SomeErr is a subclass of a removed alias that no longer inherits from OSError in a future Python version, making the fix forward-incompatible if the alias is deleted.
  • If a comment like `# Prefer IOError for clarity` appears after `raise IOError`, the autofix may preserve the comment but it becomes misleading post-rewrite, constituting a documentation hazard.
  • In `try: ... except IOError: ... finally:`, the rewrite to `OSError` does not affect finally execution, but if the except block was relying on IOError being a distinct type for logging or metrics, the semantics of error categorization change.
  • When `EnvironmentError` is used in a tuple like `except (EnvironmentError, KeyboardInterrupt):`, replacing it with `OSError` risks catching KeyboardInterrupt less likely if OSError is checked before KeyboardInterrupt in the tuple, though tuple order is preserved so this only matters if the interpreter reorders—which it doesn't, but the hazard is perceived reordering risk.
  • If `IOError` is dynamically looked up via `getattr(exceptions_module, 'IOError')` and the rewrite incorrectly modifies the string literal, it would break runtime attribute access, though the rule does not target string literals—this hazard applies only if the autofix erroneously extends beyond identifiers.
  • In a class definition like `class CustomIOError(IOError):`, rewriting the base to `OSError` is correct and safe, but if the class relied on IOError being a distinct type for `__instancecheck__` overrides, the change could break custom behavior, though no such override exists in standard aliases.

Programs

idresulttitlehazard
r1-p001fix okComments attached to rewritten codecomments and docstrings attached to the removed or moved code
r1-p002silent prediction offCustom subclass inheriting from aliasIn a class definition like `class CustomIOError(IOError):`, rewriting the base to `OSError` is correct and safe, but if the class relied on IOError being a distinct type for `__instancecheck__` overri
r1-p003fix okTuple order in except clause with KeyboardInterruptWhen `EnvironmentError` is used in a tuple like `except (EnvironmentError, KeyboardInterrupt):`, replacing it with `OSError` risks catching KeyboardInterrupt less likely if OSError is checked before K
r1-p004fix okUnreachable except clause due to subclass precedenceIn `except (IOError, ValueError):`, replacing IOError with OSError may cause ValueError to be unreachable if OSError is checked first, due to exception matching order changing subclass precedence.
r1-p005fix okException chaining in finally/else clausesexceptions raised inside the rewritten code, finally/else clauses, exception chaining
r1-p006silent prediction offpathlib vs os.path behavior on relative pathspathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths
r1-p007silent prediction offBroader except above narrowing control flowRewriting `except IOError:` to `except OSError:` changes the set of caught exceptions if other OSError subclasses (like FileNotFoundError) were previously excluded by a broader except clause above, al
r1-p008silent prediction offEvaluation order and side effects in expressionsevaluation order and side effects when expressions are merged, duplicated or deleted
r2-p001silentDynamic lookup via getattrIf autofix erroneously rewrites string literals like 'IOError' during dynamic attribute access, it breaks getattr-based lookups.
r2-p002fix okAsync function with await orderingRewriting exception types in async contexts should not interfere with await execution order or generator state.
r2-p003fix okFinally block reliance on exception type for loggingChanging except IOError to except OSError alters which exceptions are caught, affecting whether finally runs after a handled exception vs propagating.
r2-p004fix okClass attribute descriptor with side effects on accessIf the rewritten exception type is used in a descriptor's __get__ with side effects, changing it could alter when/ how many times the descriptor fires.
r2-p005silent prediction offKeyword-only args with default evaluation onceDefault values for keyword-only args are evaluated once at def-time; if the rewritten expression is in a default, it could capture stale state.
r2-p006fix okGenerator vs list consumption lazinessIf the rewritten expression appears in a generator condition, changing it could alter when the generator yields vs returns, affecting iteration timing.
r2-p007fix okBroad except above narrowing control flow with subclass precedenceRewriting except IOError to except OSError in a narrow handler above a broad OSError handler can cause the broad handler to catch exceptions intended for the narrow one, reversing control flow.
r2-p008fix ok prediction offString formatting edge cases with % and .formatIf the autofix touches string literals near exception handlers (e.g., in log messages), it could corrupt formatting directives or nested braces.

Sources

r1-p001 · Comments attached to rewritten code
1# This comment explains why we handle IOError
2# Specifically for legacy file operations
3raise IOError("legacy error") # Keep this message
--- orig.py	2026-10-17 17:18:53.256973814 -0400
+++ fixed.py	2026-10-17 17:18:53.645953769 -0400
@@ -1,3 +1,3 @@
 # This comment explains why we handle IOError
 # Specifically for legacy file operations
-raise IOError("legacy error")  # Keep this message
+raise OSError("legacy error")  # Keep this message
r1-p002 · Custom subclass inheriting from alias
1class CustomIOError(IOError): # RULE SHOULD REWRITE BASE TO OSError
2 pass
3
4try:
5 raise CustomIOError("custom")
6except OSError as e:
7 print(f"Caught: {type(e).__name__}")
8except Exception as e:
9 print(f"Unexpected: {type(e).__name__}")
Caught: CustomIOError
r1-p003 · Tuple order in except clause with KeyboardInterrupt
1import sys
2try:
3 raise KeyboardInterrupt("user abort")
4except (EnvironmentError, KeyboardInterrupt) as e: # RULE REWRITES EnvironmentError -> OSError
5 print(f"Caught: {type(e).__name__}: {e}")
--- orig.py	2026-10-17 17:18:53.989936042 -0400
+++ fixed.py	2026-10-17 17:18:54.144928068 -0400
@@ -1,5 +1,5 @@
 import sys
 try:
     raise KeyboardInterrupt("user abort")
-except (EnvironmentError, KeyboardInterrupt) as e:  # RULE REWRITES EnvironmentError -> OSError
+except (OSError, KeyboardInterrupt) as e:  # RULE REWRITES EnvironmentError -> OSError
     print(f"Caught: {type(e).__name__}: {e}")
Caught: KeyboardInterrupt: user abort
r1-p004 · Unreachable except clause due to subclass precedence
1try:
2 raise ValueError("value issue")
3except (IOError, ValueError) as e: # RULE REWRITES IOError -> OSError; OSError is broader, but ValueError not subclass -> still caught
4 print(f"Caught: {type(e).__name__}")
--- orig.py	2026-10-17 17:18:54.246922821 -0400
+++ fixed.py	2026-10-17 17:18:54.409914436 -0400
@@ -1,4 +1,4 @@
 try:
     raise ValueError("value issue")
-except (IOError, ValueError) as e:  # RULE REWRITES IOError -> OSError; OSError is broader, but ValueError not subclass -> still caught
+except (OSError, ValueError) as e:  # RULE REWRITES IOError -> OSError; OSError is broader, but ValueError not subclass -> still caught
     print(f"Caught: {type(e).__name__}")
Caught: ValueError
r1-p005 · Exception chaining in finally/else clauses
1try:
2 raise IOError("original") # RULE REWRITES IOError -> OSError
3except OSError as e:
4 try:
5 raise ValueError("chained") from e
6 finally:
7 print("finally executed")
8else:
9 print("no exception")
--- orig.py	2026-10-17 17:18:54.513909086 -0400
+++ fixed.py	2026-10-17 17:18:54.919888202 -0400
@@ -1,5 +1,5 @@
 try:
-    raise IOError("original")  # RULE REWRITES IOError -> OSError
+    raise OSError("original")  # RULE REWRITES IOError -> OSError
 except OSError as e:
     try:
         raise ValueError("chained") from e
finally executed
r1-p006 · pathlib vs os.path behavior on relative paths
1import os
2import pathlib
3try:
4 pathlib.Path("nonexistent.txt").read_text()
5except OSError as e: # RULE MAY HAVE COME FROM EnvironmentError or similar
6 print(f"pathlib: {type(e).__name__}")
7try:
8 open("nonexistent.txt").read()
9except OSError as e:
10 print(f"open: {type(e).__name__}")
pathlib: FileNotFoundError
open: FileNotFoundError
r1-p007 · Broader except above narrowing control flow
1try:
2 raise FileNotFoundError("missing")
3except OSError:
4 print("caught by OSError")
5except FileNotFoundError as e: # THIS IS NOW UNREACHABLE if above except IOError was rewritten to OSError
6 print("caught by FileNotFoundError")
caught by OSError
r1-p008 · Evaluation order and side effects in expressions
1def side_effect():
2 print("side effect")
3 return IOError
4
5try:
6 raise side_effect()("delayed") # RULE REWRITES the returned IOError -> OSError; side effect must happen
7except OSError as e:
8 print(f"Caught after side effect: {type(e).__name__}")
side effect
Caught after side effect: OSError
r2-p001 · Dynamic lookup via getattr
1import builtins
2mod = builtins
3err = getattr(mod, 'IOError')
4try:
5 raise err
6except OSError as e:
7 print('Caught:', type(e).__name__)
8except Exception as e:
9 print('Unexpected:', type(e).__name__)
Caught: OSError
r2-p002 · Async function with await ordering
1async def foo():
2 try:
3 await bar()
4 except IOError as e:
5 print('caught IOError:', e)
6 except OSError as e:
7 print('caught OSError:', e)
8async def bar():
9 raise IOError('disk error')
10import asyncio
11asyncio.run(foo())
--- orig.py	2026-10-17 17:19:01.371558476 -0400
+++ fixed.py	2026-10-17 17:19:01.683542626 -0400
@@ -1,11 +1,11 @@
 async def foo():
     try:
         await bar()
-    except IOError as e:
+    except OSError as e:
         print('caught IOError:', e)
     except OSError as e:
         print('caught OSError:', e)
 async def bar():
-    raise IOError('disk error')
+    raise OSError('disk error')
 import asyncio
 asyncio.run(foo())
caught IOError: disk error
r2-p003 · Finally block reliance on exception type for logging
1logged = []
2def log_exc(t):
3 logged.append(t)
4try:
5 raise FileNotFoundError('missing')
6except IOError:
7 log_exc('IOError-handled')
8except OSError as e:
9 log_exc('OSError-handled:', e.__class__.__name__)
10finally:
11 log_exc('finally')
12print('log:', logged)
--- orig.py	2026-10-17 17:19:01.855533888 -0400
+++ fixed.py	2026-10-17 17:19:02.017525661 -0400
@@ -3,7 +3,7 @@
     logged.append(t)
 try:
     raise FileNotFoundError('missing')
-except IOError:
+except OSError:
     log_exc('IOError-handled')
 except OSError as e:
     log_exc('OSError-handled:', e.__class__.__name__)
log: ['IOError-handled', 'finally']
r2-p004 · Class attribute descriptor with side effects on access
1class ExcSelector:
2 def __get__(self, obj, owner):
3 print('ExcSelector accessed')
4 return IOError
5class Test:
6 exc = ExcSelector()
7try:
8 raise Test.exc('fail')
9except IOError as e:
10 print('caught:', e)
11except OSError as e:
12 print('caught OS:', e)
--- orig.py	2026-10-17 17:19:02.121520387 -0400
+++ fixed.py	2026-10-17 17:19:02.281512273 -0400
@@ -6,7 +6,7 @@
     exc = ExcSelector()
 try:
     raise Test.exc('fail')
-except IOError as e:
+except OSError as e:
     print('caught:', e)
 except OSError as e:
     print('caught OS:', e)
ExcSelector accessed
caught: fail
r2-p005 · Keyword-only args with default evaluation once
1def make_handler(*,
2 exc_type=IOError,
3 msg='default'):
4 try:
5 raise exc_type(msg)
6 except exc_type as e:
7 return f'handled {type(e).__name__}'
8 except OSError as e:
9 return f'OSError fallback {type(e).__name__}'
10print(make_handler())
11print(make_handler(exc_type=OSError))
handled OSError
handled OSError
r2-p006 · Generator vs list consumption laziness
1def gen():
2 for i in range(3):
3 if i == 1:
4 raise IOError('at 1')
5 yield i
6g = gen()
7try:
8 for x in g:
9 print('yielded:', x)
10except IOError as e:
11 print('gen IOError:', e)
12except OSError as e:
13 print('gen OSError:', e)
--- orig.py	2026-10-17 17:19:02.521500103 -0400
+++ fixed.py	2026-10-17 17:19:02.682491939 -0400
@@ -1,13 +1,13 @@
 def gen():
     for i in range(3):
         if i == 1:
-            raise IOError('at 1')
+            raise OSError('at 1')
         yield i
 g = gen()
 try:
     for x in g:
         print('yielded:', x)
-except IOError as e:
+except OSError as e:
     print('gen IOError:', e)
 except OSError as e:
     print('gen OSError:', e)
yielded: 0
gen IOError: at 1
r2-p007 · Broad except above narrowing control flow with subclass precedence
1try:
2 raise FileNotFoundError('no such file')
3except IOError:
4 print('narrow IOError')
5except OSError:
6 print('broad OSError')
7except Exception as e:
8 print('unexpected:', type(e).__name__)
--- orig.py	2026-10-17 17:19:02.786486665 -0400
+++ fixed.py	2026-10-17 17:19:02.948478450 -0400
@@ -1,6 +1,6 @@
 try:
     raise FileNotFoundError('no such file')
-except IOError:
+except OSError:
     print('narrow IOError')
 except OSError:
     print('broad OSError')
narrow IOError
r2-p008 · String formatting edge cases with % and .format
1try:
2 raise IOError('error %d' % 42)
3except IOError as e:
4 print('msg:', e)
5except OSError as e:
6 print('OSError msg:', str(e))
7try:
8 raise EnvironmentError('{0}!')
9except EnvironmentError as e:
10 print('fmt:', e.format() if hasattr(e, 'format') else str(e))
11except OSError as e:
12 print('OSError fmt:', str(e))
--- orig.py	2026-10-17 17:19:03.048473384 -0400
+++ fixed.py	2026-10-17 17:19:03.219464728 -0400
@@ -1,12 +1,12 @@
 try:
-    raise IOError('error %d' % 42)
-except IOError as e:
+    raise OSError('error %d' % 42)
+except OSError as e:
     print('msg:', e)
 except OSError as e:
     print('OSError msg:', str(e))
 try:
-    raise EnvironmentError('{0}!')
-except EnvironmentError as e:
+    raise OSError('{0}!')
+except OSError as e:
     print('fmt:', e.format() if hasattr(e, 'format') else str(e))
 except OSError as e:
     print('OSError fmt:', str(e))
msg: error 42
fmt: {0}!