-
Notifications
You must be signed in to change notification settings - Fork 21
fix: handle assertThrows variable assignment in Java instrumentation #1443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: omni-java
Are you sure you want to change the base?
fix: handle assertThrows variable assignment in Java instrumentation #1443
Conversation
When assertThrows was assigned to a variable to validate exception
properties, the transformation generated invalid Java syntax by
replacing the assertThrows call with try-catch while leaving the
variable assignment intact.
Example of invalid output:
IllegalArgumentException e = try { code(); } catch (Exception) {}
This fix detects variable assignments, extracts the exception type
from assertThrows arguments, and generates proper exception capture:
IllegalArgumentException e = null;
try { code(); } catch (IllegalArgumentException _cf_caught1) { e = _cf_caught1; } catch (Exception _cf_ignored1) {}
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
⚡️ Codeflash found optimizations for this PR📄 33% (0.33x) speedup for
|
| current = [] | ||
| parts = [] | ||
|
|
||
| for char in args_content: | ||
| if char in "(<": | ||
| depth += 1 | ||
| current.append(char) | ||
| elif char in ")>": | ||
| depth -= 1 | ||
| current.append(char) | ||
| elif char == "," and depth == 0: | ||
| parts.append("".join(current).strip()) | ||
| current = [] | ||
| else: | ||
| current.append(char) | ||
|
|
||
| if current: | ||
| parts.append("".join(current).strip()) | ||
|
|
||
| if parts: | ||
| exception_arg = parts[0].strip() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⚡️Codeflash found 104% (1.04x) speedup for JavaAssertTransformer._extract_exception_class in codeflash/languages/java/remove_asserts.py
⏱️ Runtime : 9.86 milliseconds → 4.84 milliseconds (best of 162 runs)
📝 Explanation and details
The optimized code achieves a 103% speedup (2x faster) by eliminating unnecessary string building operations and using early termination.
Key Optimization:
The original code builds intermediate data structures (current list and parts list) by iterating through the entire input string, joining characters, and then extracting the first element. The optimized version directly slices the input string up to the first comma at depth 0, avoiding:
- List append operations for every character (~186K operations in profiling)
- String join operations to reconstruct parts
- Multiple intermediate list allocations
Why This Is Faster:
- Early termination: When the first comma at depth 0 is found, the optimized code immediately returns instead of processing the entire string
- Direct string slicing: Uses
args_content[:i]instead of building character lists and joining them - Reduced operations: Line profiler shows ~101K iterations vs ~192K in the original (47% fewer character iterations)
Impact on Test Cases:
- Simple cases: 29-97% faster (e.g.,
test_basic_simple_exception_extraction: 6.57μs → 3.69μs) - Complex nested structures: 60-119% faster (e.g.,
test_edge_nested_parentheses_in_second_argument_do_not_affect_split: 7.49μs → 2.85μs) - Large-scale workloads: Dramatic gains - up to 60,162% faster for the 1000-argument test case (2.22ms → 3.68μs), where early termination provides massive benefits
- Edge cases with no comma: Still faster (22-29%) due to simpler string operations
The optimization is particularly effective for assertThrows parsing in Java test code where arguments after the exception class can be arbitrarily complex (nested lambdas, method calls with many parameters). The early-exit strategy means performance scales with the position of the first comma rather than total input length.
✅ Correctness verification report:
| Test | Status |
|---|---|
| ⚙️ Existing Unit Tests | 🔘 None Found |
| 🌀 Generated Regression Tests | ✅ 4085 Passed |
| ⏪ Replay Tests | 🔘 None Found |
| 🔎 Concolic Coverage Tests | 🔘 None Found |
| 📊 Tests Coverage | 100.0% |
🌀 Click to see Generated Regression Tests
import pytest # used for our unit tests
from codeflash.languages.java.remove_asserts import JavaAssertTransformer
# Create a transformer instance for use in tests (uses real JavaAnalyzer via get_java_analyzer)
# We pass a dummy function name because the tested method is independent of it.
_transformer = JavaAssertTransformer("assertThrows")
def test_basic_simple_exception_extraction():
# Simple, typical usage: exception class followed by other arguments.
s = "IllegalArgumentException.class, () -> { /* lambda */ }"
# Expect the class name without the `.class` suffix.
codeflash_output = _transformer._extract_exception_class(s) # 6.57μs -> 3.69μs (78.0% faster)
def test_basic_fully_qualified_class():
# Fully-qualified class names should be preserved (packages included).
s = "com.example.errors.MyException.class, () -> {}"
# Expect the fully-qualified name minus `.class`.
codeflash_output = _transformer._extract_exception_class(s) # 6.08μs -> 4.05μs (50.3% faster)
def test_basic_only_class_no_comma():
# If only the exception class is provided (no comma), it should still be extracted.
s = "MyCustomException.class"
# Expect the bare class name.
codeflash_output = _transformer._extract_exception_class(s) # 3.84μs -> 2.98μs (29.0% faster)
def test_edge_empty_string_returns_none():
# Empty content inside assertThrows should return None.
s = ""
# No first argument => function must return None.
codeflash_output = _transformer._extract_exception_class(s) # 808ns -> 848ns (4.72% slower)
def test_edge_first_argument_missing_class_suffix_returns_none():
# If the first argument doesn't end with `.class`, it's not recognized as an exception class.
s = "new IllegalArgumentException(), () -> {}"
# Should return None because the first part is not a `.class` reference.
codeflash_output = _transformer._extract_exception_class(s) # 5.41μs -> 3.78μs (43.2% faster)
def test_edge_comma_inside_generics_of_first_argument_is_handled():
# First argument contains generics with a comma; splitting must not break on that comma.
s = "Map.Entry<String, Integer>.class, someOtherCall(arg1, arg2)"
# The function should return the entire generic-qualified first argument (minus `.class`).
codeflash_output = _transformer._extract_exception_class(s) # 7.07μs -> 4.09μs (72.8% faster)
def test_edge_nested_parentheses_in_second_argument_do_not_affect_split():
# Second argument contains nested parentheses and commas which must be ignored by the top-level split.
s = "MyException.class, () -> doSomething(foo(bar, baz), another)"
# Ensure the first argument was correctly identified.
codeflash_output = _transformer._extract_exception_class(s) # 7.49μs -> 2.85μs (163% faster)
def test_edge_generic_class_with_angle_brackets_is_returned_intact():
# Generic type references on the class should be returned as-is (with angle brackets).
s = "Outer.Inner<GenericType>.class, someOther"
# The returned value should keep the angle-bracketed generic.
codeflash_output = _transformer._extract_exception_class(s) # 5.58μs -> 3.77μs (48.3% faster)
def test_edge_leading_trailing_whitespace_is_stripped():
# Whitespace around arguments should be trimmed by the function.
s = " WeirdException .class , () -> {}"
# Note: the function strips only after removing .class; it will also strip whitespace from ends.
codeflash_output = _transformer._extract_exception_class(s) # 5.79μs -> 3.72μs (55.7% faster)
def test_edge_none_input_raises_type_error():
# Passing None is a misuse; iterating over None should raise a TypeError.
with pytest.raises(TypeError):
_transformer._extract_exception_class(None) # 2.61μs -> 2.71μs (3.70% slower)
def test_edge_non_string_bytes_input_raises_type_error():
# Passing bytes is not supported; the function expects a str and will fail during processing/join.
with pytest.raises(TypeError):
_transformer._extract_exception_class(b"MyException.class, arg") # 3.31μs -> 3.40μs (2.76% slower)
def test_large_scale_many_trailing_arguments_and_nested_structures():
# Construct a long argument list (1000 items) where only the first is the exception .class reference.
tail_parts = []
for i in range(1000):
# Each trailing argument contains nested parentheses and commas to stress the parser logic.
tail_parts.append(f"fn{i}(arg{i}, nested({i}, {i+1}))")
long_args = ", ".join(tail_parts)
s = "MassiveException.class, " + long_args
# The function should still correctly extract the first `.class` reference.
codeflash_output = _transformer._extract_exception_class(s) # 2.22ms -> 3.68μs (60162% faster)
def test_large_scale_repeated_calls_stability_and_correctness():
# Call the method repeatedly (1000 iterations) with a variety of valid inputs to ensure consistency.
inputs = [
"A.class, x",
"pkg.B.class, () -> {}",
"C<D, E>.class, something(1,2)",
"Single.class"
]
# Loop many times to detect any stateful behavior or performance regressions.
for _ in range(1000):
# Check all inputs in every iteration
codeflash_output = _transformer._extract_exception_class(inputs[0]) # 1.26ms -> 971μs (30.2% faster)
codeflash_output = _transformer._extract_exception_class(inputs[1])
codeflash_output = _transformer._extract_exception_class(inputs[2]) # 2.01ms -> 1.19ms (69.1% faster)
codeflash_output = _transformer._extract_exception_class(inputs[3])
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.import pytest
from codeflash.languages.java.parser import JavaAnalyzer
from codeflash.languages.java.remove_asserts import JavaAssertTransformer
# Basic tests - verify fundamental functionality under normal conditions
def test_extract_exception_class_simple_exception():
"""Test extracting a simple exception class name from assertThrows."""
transformer = JavaAssertTransformer("test_method")
# Input: "IllegalArgumentException.class, () -> method()"
codeflash_output = transformer._extract_exception_class("IllegalArgumentException.class, () -> method()"); result = codeflash_output # 6.10μs -> 3.79μs (61.0% faster)
def test_extract_exception_class_with_package():
"""Test extracting fully qualified exception class name."""
transformer = JavaAssertTransformer("test_method")
# Input with package prefix
codeflash_output = transformer._extract_exception_class("java.lang.IllegalArgumentException.class, () -> method()"); result = codeflash_output # 6.90μs -> 4.33μs (59.1% faster)
def test_extract_exception_class_runtime_exception():
"""Test extracting RuntimeException."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("RuntimeException.class, () -> {}"); result = codeflash_output # 5.05μs -> 3.28μs (54.0% faster)
def test_extract_exception_class_null_pointer_exception():
"""Test extracting NullPointerException."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("NullPointerException.class, () -> foo()"); result = codeflash_output # 5.65μs -> 3.47μs (63.1% faster)
def test_extract_exception_class_custom_exception():
"""Test extracting a custom exception class."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("com.example.CustomException.class, () -> doSomething()"); result = codeflash_output # 6.67μs -> 3.83μs (74.1% faster)
def test_extract_exception_class_with_lambda():
"""Test extracting exception class when lambda has no arguments."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IOException.class, () -> readFile()"); result = codeflash_output # 5.36μs -> 2.98μs (80.2% faster)
def test_extract_exception_class_with_method_call():
"""Test extracting exception class with method call in lambda."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalStateException.class, () -> obj.method()"); result = codeflash_output # 5.97μs -> 3.50μs (70.6% faster)
def test_extract_exception_class_with_lambda_body():
"""Test extracting exception class with complex lambda body."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalArgumentException.class, () -> { throw new RuntimeException(); }"); result = codeflash_output # 7.59μs -> 3.65μs (108% faster)
# Edge tests - evaluate behavior under extreme or unusual conditions
def test_extract_exception_class_only_exception_no_lambda():
"""Test extracting exception class when only exception is provided (no lambda)."""
transformer = JavaAssertTransformer("test_method")
# Only the exception class, no second argument
codeflash_output = transformer._extract_exception_class("NullPointerException.class"); result = codeflash_output # 4.04μs -> 3.29μs (22.6% faster)
def test_extract_exception_class_empty_string():
"""Test with empty string input."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class(""); result = codeflash_output # 817ns -> 817ns (0.000% faster)
def test_extract_exception_class_no_dot_class_suffix():
"""Test with exception class name but no .class suffix."""
transformer = JavaAssertTransformer("test_method")
# Missing .class suffix
codeflash_output = transformer._extract_exception_class("IllegalArgumentException, () -> method()"); result = codeflash_output # 5.42μs -> 3.22μs (68.4% faster)
def test_extract_exception_class_only_dot_class():
"""Test with only '.class' string."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class(".class"); result = codeflash_output # 2.66μs -> 2.18μs (21.9% faster)
def test_extract_exception_class_whitespace_before_exception():
"""Test with leading whitespace before exception class."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class(" IllegalArgumentException.class, () -> method()"); result = codeflash_output # 6.24μs -> 3.91μs (59.5% faster)
def test_extract_exception_class_whitespace_around_comma():
"""Test with whitespace around comma separator."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalArgumentException.class , () -> method()"); result = codeflash_output # 6.45μs -> 3.88μs (66.0% faster)
def test_extract_exception_class_nested_parentheses_in_lambda():
"""Test with nested parentheses in lambda expression."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalArgumentException.class, () -> method(param(a, b))"); result = codeflash_output # 6.93μs -> 3.63μs (91.1% faster)
def test_extract_exception_class_nested_angle_brackets():
"""Test with nested angle brackets (generics) in arguments."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalArgumentException.class, () -> List<String> list = new ArrayList<>()"); result = codeflash_output # 8.05μs -> 3.68μs (119% faster)
def test_extract_exception_class_mixed_depth_nesting():
"""Test with mixed nesting of parentheses and angle brackets."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalArgumentException.class, () -> method(List<String> param)"); result = codeflash_output # 7.20μs -> 3.64μs (97.8% faster)
def test_extract_exception_class_multiple_commas_in_lambda():
"""Test with multiple commas inside lambda parentheses."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalArgumentException.class, () -> method(a, b, c)"); result = codeflash_output # 7.00μs -> 3.54μs (97.7% faster)
def test_extract_exception_class_no_space_after_dot_class():
"""Test with no space after .class."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalArgumentException.class,() -> method()"); result = codeflash_output # 5.88μs -> 3.63μs (62.0% faster)
def test_extract_exception_class_exception_with_numbers():
"""Test exception class name containing numbers."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalArgumentException2.class, () -> method()"); result = codeflash_output # 5.85μs -> 3.63μs (60.9% faster)
def test_extract_exception_class_deeply_nested_generics():
"""Test with deeply nested generic types."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalArgumentException.class, () -> Map<String, List<Integer>> map = new HashMap<>()"); result = codeflash_output # 9.30μs -> 3.54μs (163% faster)
def test_extract_exception_class_with_inner_class():
"""Test exception class that is an inner class."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("OuterException.InnerException.class, () -> method()"); result = codeflash_output # 6.29μs -> 3.91μs (60.7% faster)
def test_extract_exception_class_exception_only_with_trailing_comma():
"""Test when only exception is present but followed by comma."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("NullPointerException.class,"); result = codeflash_output # 4.20μs -> 3.47μs (21.0% faster)
def test_extract_exception_class_very_long_package_name():
"""Test with very long fully qualified package name."""
transformer = JavaAssertTransformer("test_method")
long_name = "com.example.test.util.exception.custom.VeryLongExceptionClassName.class, () -> method()"
codeflash_output = transformer._extract_exception_class(long_name); result = codeflash_output # 9.22μs -> 6.18μs (49.1% faster)
def test_extract_exception_class_exception_with_underscores():
"""Test exception class name with underscores."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("Illegal_Argument_Exception.class, () -> method()"); result = codeflash_output # 6.14μs -> 3.77μs (62.7% faster)
def test_extract_exception_class_single_letter_class():
"""Test single letter exception class name."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("E.class, () -> method()"); result = codeflash_output # 4.45μs -> 2.26μs (97.1% faster)
def test_extract_exception_class_multiple_spaces_between_parts():
"""Test with multiple spaces between exception and comma."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalArgumentException.class , () -> method()"); result = codeflash_output # 6.46μs -> 4.01μs (61.2% faster)
def test_extract_exception_class_tabs_and_newlines():
"""Test with tabs and newlines in the input."""
transformer = JavaAssertTransformer("test_method")
codeflash_output = transformer._extract_exception_class("IllegalArgumentException.class\t,\n() -> method()"); result = codeflash_output # 5.95μs -> 3.75μs (58.5% faster)
# Large-scale tests - assess performance and scalability
def test_extract_exception_class_very_long_lambda_body():
"""Test with a very long lambda body containing many method calls."""
transformer = JavaAssertTransformer("test_method")
# Create a long lambda with 100 method calls separated by commas
method_calls = ", ".join([f"method{i}()" for i in range(100)])
args = f"IllegalArgumentException.class, () -> {{{method_calls}}}"
codeflash_output = transformer._extract_exception_class(args); result = codeflash_output # 80.3μs -> 3.69μs (2079% faster)
def test_extract_exception_class_deeply_nested_parentheses():
"""Test with deeply nested parentheses (100 levels deep)."""
transformer = JavaAssertTransformer("test_method")
# Create 100 levels of nested parentheses
nested = "method" + "(" * 100 + ")" * 100
args = f"IllegalArgumentException.class, () -> {nested}"
codeflash_output = transformer._extract_exception_class(args); result = codeflash_output # 17.7μs -> 3.71μs (377% faster)
def test_extract_exception_class_many_commas_in_method_args():
"""Test with method calls containing 50 parameters separated by commas."""
transformer = JavaAssertTransformer("test_method")
# Create a method with 50 parameters
params = ", ".join([f"arg{i}" for i in range(50)])
args = f"IllegalArgumentException.class, () -> method({params})"
codeflash_output = transformer._extract_exception_class(args); result = codeflash_output # 33.2μs -> 3.60μs (822% faster)
def test_extract_exception_class_large_number_of_generics():
"""Test with many nested generic type parameters."""
transformer = JavaAssertTransformer("test_method")
# Create deeply nested generics
generics = "Map<String, Map<String, Map<String, Map<String, Map<String, Integer>>>>>"
args = f"IllegalArgumentException.class, () -> {generics} map = new HashMap<>()"
codeflash_output = transformer._extract_exception_class(args); result = codeflash_output # 12.9μs -> 3.57μs (263% faster)
def test_extract_exception_class_multiple_consecutive_calls():
"""Test multiple consecutive calls maintain consistency."""
transformer = JavaAssertTransformer("test_method")
# Call multiple times with same input - should always return same result
test_input = "IllegalArgumentException.class, () -> method()"
results = [transformer._extract_exception_class(test_input) for _ in range(1000)]
def test_extract_exception_class_varied_exception_types_batch():
"""Test extraction of 100 different exception types."""
transformer = JavaAssertTransformer("test_method")
exceptions = [f"Exception{i}.class, () -> method()" for i in range(100)]
results = [transformer._extract_exception_class(exc) for exc in exceptions]
def test_extract_exception_class_with_very_long_package_path():
"""Test with package path containing 50 segments."""
transformer = JavaAssertTransformer("test_method")
package_parts = ".".join([f"segment{i}" for i in range(50)])
full_class = f"{package_parts}.MyException"
args = f"{full_class}.class, () -> method()"
codeflash_output = transformer._extract_exception_class(args); result = codeflash_output # 35.8μs -> 31.0μs (15.2% faster)
def test_extract_exception_class_alternating_parentheses_and_angles():
"""Test with alternating parentheses and angle brackets 50 times."""
transformer = JavaAssertTransformer("test_method")
# Create alternating pattern: <...>, (...), <...>, (...)
pattern = "".join([f"<{i}>" if i % 2 == 0 else f"({i})" for i in range(50)])
args = f"IllegalArgumentException.class, () -> method{pattern}"
codeflash_output = transformer._extract_exception_class(args); result = codeflash_output # 18.0μs -> 3.58μs (402% faster)
def test_extract_exception_class_stress_test_boundary():
"""Stress test with all edge cases combined in single input."""
transformer = JavaAssertTransformer("test_method")
# Complex nested structure with multiple edge cases
args = (
"com.example.test.VeryLongExceptionName_With_Underscores123.class, "
"() -> method(" + ", ".join([f"arg{i}" for i in range(100)]) + ")"
)
codeflash_output = transformer._extract_exception_class(args); result = codeflash_output # 62.9μs -> 5.59μs (1024% faster)
def test_extract_exception_class_performance_many_similar_inputs():
"""Test performance with 500 similar inputs containing various whitespace."""
transformer = JavaAssertTransformer("test_method")
test_cases = []
for i in range(500):
# Vary whitespace but keep exception name same
spaces_before = " " * (i % 10)
spaces_after = " " * ((i + 1) % 10)
args = f"{spaces_before}IllegalArgumentException.class{spaces_after}, () -> method()"
test_cases.append(args)
results = [transformer._extract_exception_class(args) for args in test_cases]
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.To test or edit this optimization locally git merge codeflash/optimize-pr1443-2026-02-10T21.40.29
Click to see suggested changes
| current = [] | |
| parts = [] | |
| for char in args_content: | |
| if char in "(<": | |
| depth += 1 | |
| current.append(char) | |
| elif char in ")>": | |
| depth -= 1 | |
| current.append(char) | |
| elif char == "," and depth == 0: | |
| parts.append("".join(current).strip()) | |
| current = [] | |
| else: | |
| current.append(char) | |
| if current: | |
| parts.append("".join(current).strip()) | |
| if parts: | |
| exception_arg = parts[0].strip() | |
| for i, char in enumerate(args_content): | |
| if char in "(<": | |
| depth += 1 | |
| elif char in ")>": | |
| depth -= 1 | |
| elif char == "," and depth == 0: | |
| exception_arg = args_content[:i].strip() | |
| # Remove .class suffix | |
| if exception_arg.endswith(".class"): | |
| return exception_arg[:-6].strip() | |
| return None | |
| if args_content: | |
| exception_arg = args_content.strip() |
⚡️ Codeflash found optimizations for this PR📄 14% (0.14x) speedup for
|
Problem
When
assertThrowswas assigned to a variable to validate exception properties, the assertion transformation generated invalid Java syntax that failed Maven compilation:This occurred because the transformation replaced only the
assertThrows(...)call with a try-catch block, leaving the variable assignment intact. In Java, you cannot assign a try-catch statement to a variable.Root Cause
The regex pattern in
_find_junit_assertions()matched only theassertThrows(...)method call, not the preceding variable assignment. The replacement logic in_generate_exception_replacement()generatedtry { code } catch (Exception) {}without considering that the assertion might be assigned to a variable.Resolution
Added logic to:
assertThrowscallsassertThrowsargumentsThis preserves subsequent assertions on the exception object (e.g.,
exception.getMessage()).Testing
test_java_assertion_removal.pypass🤖 Generated with Claude Code