fixhunt

ruff FURB103

labelled unknown
Runs / 2026-10-27T22-05-04-159Z-hoy6-ruff-FURB103ruff 0.17.12026-10-27 22:05 UTC10 programs × 2 round(s)20 programs, 6 fired, 3 fixed, 0 findings, 9 rejectedmodel cost $0.0076

Analysis Nemotron 3 Super

Flags patterns where a file is opened using built-in open() and written to via .write() inside a with statement, suggesting replacement with pathlib.Path.write_text() or Path.write_bytes().

Fix: Replaces the with-open-write block with a call to pathlib.Path.write_text() if the mode is text-based (e.g., 'w', 'wt', 'a', 'at') or Path.write_bytes() if the mode is binary (e.g., 'wb', 'ab'), preserving the file path and the argument to write(), assuming the path is a string or Path-compatible object and the write argument matches the expected type.

10 hazards
  • The rewrite assumes the file path is a simple string or Path object; if it is an expression with side effects (e.g., a function call), evaluating it once in the Path constructor may change behavior if the original with statement evaluated it multiple times due to __enter__/__exit__ logic.
  • If the file path is not a string or bytes (e.g., an integer or custom object), Path() construction may raise TypeError where the original open() might have succeeded via __fspath__ or other protocol support.
  • The rewrite does not preserve any comments attached to the with statement or the open() call, which may be lost during autofix.
  • If the write() argument is not a literal or simple variable but an expression with side effects (e.g., f.write(get_data())), the autofix still moves it, but if the original code relied on the timing of evaluation within the context manager (e.g., for locking or state), moving it outside could alter behavior.
  • The rewrite does not account for custom open overrides via imports (e.g., from mymodule import open), which may not behave like built-in open and thus invalidate the Path substitution.
  • If the mode includes flags like '+' (e.g., 'w+', 'r+'), the rule should not fire, but if it does due to incorrect detection, the rewrite would lose read capability.
  • The rewrite ignores buffering, errors, and newline parameters; while these often have defaults in write_text/write_bytes, explicit non-default values may change behavior (e.g., custom error handling).
  • If the file object is aliased elsewhere in the block (e.g., global f = f) and used after the write, the rewrite breaks because the Path object does not expose the same file handle semantics.
  • The autofix may generate invalid syntax if the path contains characters that require escaping in a string literal but are not properly handled (though this is rare and depends on input).
  • If the code is part of a larger suite where the file is reopened or reused later, replacing the open with a one-time write may interfere with subsequent operations expecting an open handle.

Programs

idresulttitlehazard
r1-p001invalidstar_imports_module_level_side_effects___all__star-imports, module-level side effects, __all__
r1-p002invalidshadowed_builtins_and_names_redefined_later_in_the_moduleshadowed builtins and names redefined later in the module
r1-p003invalidwalrus_comprehensions_scoping_late_binding_in_closures_and_lambdaswalrus, comprehensions scoping, late binding in closures and lambdas
r1-p004invalidcontext_managers_that_are_not_files_objects_with_write_no_closecontext managers that are not files, objects with write() but no close()
r1-p005fired, no fixrewrite_does_not_preserve_comments_attached_to_with_or_openThe rewrite does not preserve any comments attached to the with statement or the open() call, which may be lost during autofix.
r1-p006invalidfile_object_aliased_elsewhere_in_block_used_after_writeIf the file object is aliased elsewhere in the block (e.g., global f = f) and used after the write, the rewrite breaks because the Path object does not expose the same file handle semantics.
r1-p007invalidbytes_vs_str_encoding_and_newline_handling_text_vs_binary_modesbytes vs str, encoding and newline handling, text vs binary modes
r1-p008invalidpart_of_larger_suite_file_reopened_or_reused_laterIf the code is part of a larger suite where the file is reopened or reused later, replacing the open with a one-time write may interfere with subsequent operations expecting an open handle.
r1-p009invaliddict_ordering_duplicate_keys_hash_eq_overrides_setsdict ordering, duplicate keys, __hash__/__eq__ overrides, sets
r1-p010invalidfile_modes_truncation_partial_writes_non_str_data_passed_to_writefile modes and truncation, partial writes, non-str data passed to write
r2-p001silent prediction offGenerators vs Lists Iterators Consumed Oncegenerators vs lists, iterators consumed once, lazy vs eager evaluation
r2-p002fix okMutable Default Arguments Aliasing After Rewritemutable default arguments and aliasing after a rewrite
r2-p003silentCustom Open Override Invalidates Path SubstitutionThe rewrite does not account for custom open overrides via imports (e.g., from mymodule import open)
r2-p004fix okEvaluation Order Side Effects When Expressions Mergedevaluation order and side effects when expressions are merged, duplicated or deleted
r2-p005silent prediction offKeyword-Only Positional-Only Args Forwarding Defaultskeyword-only, positional-only, *args/**kwargs forwarding, defaults evaluated once
r2-p006fix okPathlib vs Os.Path Behavior Relative Symlinks Missingpathlib vs os.path behavior on relative paths, symlinks, missing files, bytes paths
r2-p007silent prediction offRewrite Ignores Buffering Errors Newline ParametersThe rewrite ignores buffering, errors, and newline parameters; while these often have defaults in write_text/write_bytes, explicit non-default values may change behavior
r2-p008fired, no fixComments Docstrings Attached To Removed Moved Codecomments and docstrings attached to the removed or moved code
r2-p009fired, no fixRewrite Does Not Preserve Comments On With Or Open CallThe rewrite does not preserve any comments attached to the with statement or the open() call, which may be lost during autofix.
r2-p010silent prediction offFile Object Aliased Elsewhere Used After Write BreaksIf the file object is aliased elsewhere in the block (e.g., global f = f) and used after the write, the rewrite breaks because the Path object does not expose the same file handle semantics.

Sources

r1-p001 · star_imports_module_level_side_effects___all__ (generated program does not parse: Traceback (most recent call last):)
1__all__ = ['path']
2def _side():
3 global wrote
4 with open(path, 'w') as f:\n # comment before write\n f.write(wrote := 'hello')\npath = 'out.txt'
5wrote = None
6_side()
7with open(path) as f:
8 print(f.read())
9import os
10os.remove(path)
r1-p002 · shadowed_builtins_and_names_redefined_later_in_the_module (generated program does not parse: Traceback (most recent call last):)
1open = lambda *a, **k: None # shadow built-in
2path = 'test.txt'
3with open(path, 'w') as f: # uses shadowed open\n f.write('shadowed') # rule fires but open is not real open\ntry:
4 with open(path) as f:\n print(f.read())\nexcept Exception as e:
5 print(type(e).__name__, e)\nopen = __builtins__.open # restore\nwith open(path, 'w') as f:\n f.write('real')\nwith open(path) as f:\n print(f.read())\nimport os\nos.remove(path)
r1-p003 · walrus_comprehensions_scoping_late_binding_in_closures_and_lambdas (generated program does not parse: Traceback (most recent call last):)
1funs = []
2for i in range(2):
3 path = f'{i}.txt'
4 with open(path, 'w') as f:\n f.write((data := str(i)) * 2)\n funs.append(lambda: open(path).read())\nprint([f() for f in funs])\nfor p in ['0.txt', '1.txt']:
5 import os\nos.remove(p)
r1-p004 · context_managers_that_are_not_files_objects_with_write_no_close (generated program does not parse: Traceback (most recent call last):)
1class FakeFile:
2 def __init__(self, name):
3 self.name = name
4 self.buf = ''
5 def __enter__(self):
6 return self
7 def __exit__(self, *args):
8 pass
9 def write(self, s):
10 self.buf += s
11with FakeFile('dummy') as f:\n f.write('test') # rule silent: not open()\nprint(f.buf)
r1-p005 · rewrite_does_not_preserve_comments_attached_to_with_or_open
1# This comment should be lost
2with open('comments.txt', 'w') as f: # inline comment
3 f.write('keep me') # another
4# after
5with open('comments.txt') as f:
6 print(f.read())
7import os
8os.remove('comments.txt')
keep me
r1-p006 · file_object_aliased_elsewhere_in_block_used_after_write (generated program does not parse: Traceback (most recent call last):)
1global f\nf = None
2with open('alias.txt', 'w') as f:\n f.write('hello')\n global f\n f = f # alias to self\nif f is not None:\n f.write(' world') # second write relies on alias\nwith open('alias.txt') as f:\n print(f.read())\nimport os\nos.remove('alias.txt')
r1-p007 · bytes_vs_str_encoding_and_newline_handling_text_vs_binary_modes (generated program does not parse: Traceback (most recent call last):)
1with open('text.txt', 'w', encoding='utf-8', newline='') as f:
2 f.write('caf\u00e9
3')
4with open('text.txt', 'rb') as f:
5 print(repr(f.read()))
6with open('binary.txt', 'wb') as f:
7 f.write(b'caf\xc3\xa9
8')
9with open('binary.txt', 'rb') as f:
10 print(repr(f.read()))
11import os
12os.remove('text.txt')
13os.remove('binary.txt')
r1-p008 · part_of_larger_suite_file_reopened_or_reused_later (generated program does not parse: Traceback (most recent call last):)
1log = 'log.txt'
2with open(log, 'a') as f:\n f.write('first\n')\n f.write('second\n') # same handle\nwith open(log) as f:\n print(f.read(), end='')\nimport os\nos.remove(log)
r1-p009 · dict_ordering_duplicate_keys_hash_eq_overrides_sets (generated program does not parse: Traceback (most recent call last):)
1class K:
2 def __init__(self, v): self.v = v
3 def __hash__(self): return hash(self.v)
4 def __eq__(self, other): return self.v == other.v
5 def __repr__(self): return f'K({self.v})'
6k1, k2 = K(1), K(1) # same hash, equal
7with open('dict.txt', 'w') as f:\n f.write(str({k1: 'a', k2: 'b'})) # dict may collapse\nwith open('dict.txt') as f:\n print(f.read())\nimport os\nos.remove('dict.txt')
r1-p010 · file_modes_truncation_partial_writes_non_str_data_passed_to_write (generated program does not parse: Traceback (most recent call last):)
1with open('mode.txt', 'w') as f:\n f.write('abcdef')\n f.seek(2)\n f.write('12') # partial overwrite\nwith open('mode.txt') as f:\n print(f.read())\ntry:
2 with open('bad.txt', 'w') as f:\n f.write(123) # type error\nexcept Exception as e:
3 print(type(e).__name__, e)\nimport os\nos.remove('mode.txt')\nif 'bad.txt' in locals(): os.remove('bad.txt')
r2-p001 · Generators vs Lists Iterators Consumed Once
1def gen_lines():
2 yield 'a'
3 yield 'b'
4with open('tmp.txt', 'w') as f:
5 for line in gen_lines():
6 f.write(line + '\\n')
7with open('tmp.txt', 'r') as f:
8 content = f.read()
9print(repr(content))
10import os
11os.remove('tmp.txt')
'a\\nb\\n'
r2-p002 · Mutable Default Arguments Aliasing After Rewrite
1def log(msg, buf=[]):
2 buf.append(msg)
3 return buf
4with open('tmp.txt', 'w') as f:
5 f.write(''.join(log('x')))
6with open('tmp.txt', 'r') as f:
7 print(repr(f.read()))
8import os
9os.remove('tmp.txt')
--- orig.py	2026-10-27 22:06:07.130765550 +0000
+++ fixed.py	2026-10-27 22:06:11.071983658 +0000
@@ -1,8 +1,8 @@
+import pathlib
 def log(msg, buf=[]):
     buf.append(msg)
     return buf
-with open('tmp.txt', 'w') as f:
-    f.write(''.join(log('x')))
+pathlib.Path('tmp.txt').write_text(''.join(log('x')))
 with open('tmp.txt', 'r') as f:
     print(repr(f.read()))
 import os
'x'
r2-p003 · Custom Open Override Invalidates Path Substitution
1def open(*args, **kwargs):
2 class FakeFile:
3 def __enter__(self): return self
4 def __exit__(self, *exc): pass
5 def write(self, data): self.data = data
6 return FakeFile()
7f = open('tmp.txt', 'w')
8with f as file:
9 file.write('hello')
10print(getattr(file, 'data', 'none'))
hello
r2-p004 · Evaluation Order Side Effects When Expressions Merged
1counter = 0
2def inc():
3 global counter
4 counter += 1
5 return counter
6with open('tmp.txt', 'w') as f:
7 f.write(str(inc()) + '\\n' + str(inc()))
8with open('tmp.txt', 'r') as f:
9 print(repr(f.read()))
10import os
11os.remove('tmp.txt')
--- orig.py	2026-10-27 22:06:07.132765661 +0000
+++ fixed.py	2026-10-27 22:06:10.335942925 +0000
@@ -1,10 +1,10 @@
+import pathlib
 counter = 0
 def inc():
     global counter
     counter += 1
     return counter
-with open('tmp.txt', 'w') as f:
-    f.write(str(inc()) + '\\n' + str(inc()))
+pathlib.Path('tmp.txt').write_text(str(inc()) + '\\n' + str(inc()))
 with open('tmp.txt', 'r') as f:
     print(repr(f.read()))
 import os
'1\\n2'
r2-p005 · Keyword-Only Positional-Only Args Forwarding Defaults
1def write_file(*, path, data, mode='w', **kw):
2 with open(path, mode, **kw) as f:
3 f.write(data)
4write_file(path='tmp.txt', data='hello', encoding='utf-8')
5with open('tmp.txt', 'r', encoding='utf-8') as f:
6 print(repr(f.read()))
7import os
8os.remove('tmp.txt')
'hello'
r2-p006 · Pathlib vs Os.Path Behavior Relative Symlinks Missing
1import os
2os.symlink('target.txt', 'link.txt')
3with open('link.txt', 'w') as f:
4 f.write('via link')
5with open('target.txt', 'r') as f:
6 print(repr(f.read()))
7os.unlink('link.txt')
8os.remove('target.txt')
--- orig.py	2026-10-27 22:06:07.133765716 +0000
+++ fixed.py	2026-10-27 22:06:10.733964952 +0000
@@ -1,7 +1,7 @@
 import os
+import pathlib
 os.symlink('target.txt', 'link.txt')
-with open('link.txt', 'w') as f:
-    f.write('via link')
+pathlib.Path('link.txt').write_text('via link')
 with open('target.txt', 'r') as f:
     print(repr(f.read()))
 os.unlink('link.txt')
'via link'
r2-p007 · Rewrite Ignores Buffering Errors Newline Parameters
1with open('tmp.txt', 'w', buffering=1, newline='\\n') as f:
2 f.write('hello')
3with open('tmp.txt', 'rb') as f:
4 print(repr(f.read()))
5import os
6os.remove('tmp.txt')
r2-p008 · Comments Docstrings Attached To Removed Moved Code
1with open('tmp.txt', 'w') as f: # write greeting
2 f.write('hi') # and newline
3with open('tmp.txt', 'r') as f:
4 print(repr(f.read()))
5import os
6os.remove('tmp.txt')
'hi'
r2-p009 · Rewrite Does Not Preserve Comments On With Or Open Call
1# important log
2with open('tmp.txt', 'w') as f: # safety first
3 f.write('ok')
4with open('tmp.txt', 'r') as f:
5 print(repr(f.read()))
6import os
7os.remove('tmp.txt')
'ok'
r2-p010 · File Object Aliased Elsewhere Used After Write Breaks
1f_handle = None
2with open('tmp.txt', 'w') as f:
3 f_handle = f
4 f.write('data')
5if not f_handle.closed:
6 f_handle.write('more')
7with open('tmp.txt', 'r') as f:
8 print(repr(f.read()))
9import os
10os.remove('tmp.txt')
'data'