Cosmic Ray Report

Date time: 04/11/2026 04:44:31

Total jobs: 1733

Complete: 1733 (100.00%)

Surviving mutants: 411 (23.72%)

operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -32,7 +32,7 @@
     """
     log_format = settings.log_format.lower()
 
-    if log_format == "json":
+    if log_format != "json":
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................................................F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________

    def test_get_log_renderer_console():
        """Test console renderer selection."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "console"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7159e46e1a90>, <class 'structlog.dev.ConsoleRenderer'>)
E            +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E            +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:46: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7159e46e1a90>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 201 passed, 3 deselected, 1 warning in 1.51s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 1, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -34,7 +34,7 @@
 
     if log_format == "json":
         return structlog.processors.JSONRenderer()
-    elif log_format == "console":
+    elif log_format != "console":
         return structlog.dev.ConsoleRenderer(colors=True)
     elif log_format == "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................................................F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________

    def test_get_log_renderer_console():
        """Test console renderer selection."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "console"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7b3328721a90>, <class 'structlog.dev.ConsoleRenderer'>)
E            +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E            +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:46: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7b3328721a90>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 201 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 2, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -36,7 +36,7 @@
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
-    elif log_format == "auto":
+    elif log_format != "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
         if is_running_in_kubernetes():
             return structlog.processors.JSONRenderer()
........................................................................ [ 15%]
........................................................................ [ 30%]
...........................................................F
=================================== FAILURES ===================================
_______________________ test_get_log_renderer_auto_local _______________________

    def test_get_log_renderer_auto_local():
        """Test auto renderer in local environment."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "auto"
            with patch("stampbot.logger.is_running_in_kubernetes", return_value=False):
                from stampbot.logger import _get_log_renderer
    
                renderer = _get_log_renderer()
>               assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E               AssertionError: assert False
E                +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x724a1b860b00>, <class 'structlog.dev.ConsoleRenderer'>)
E                +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E                +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:68: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_local - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x724a1b860b00>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 203 passed, 3 deselected, 1 warning in 1.56s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -32,7 +32,7 @@
     """
     log_format = settings.log_format.lower()
 
-    if log_format == "json":
+    if log_format < "json":
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................................................F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________

    def test_get_log_renderer_console():
        """Test console renderer selection."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "console"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x711cf58d5a90>, <class 'structlog.dev.ConsoleRenderer'>)
E            +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E            +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:46: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x711cf58d5a90>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 201 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 1, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -34,7 +34,7 @@
 
     if log_format == "json":
         return structlog.processors.JSONRenderer()
-    elif log_format == "console":
+    elif log_format < "console":
         return structlog.dev.ConsoleRenderer(colors=True)
     elif log_format == "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................................................F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________

    def test_get_log_renderer_console():
        """Test console renderer selection."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "console"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x767d6bf4da90>, <class 'structlog.dev.ConsoleRenderer'>)
E            +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E            +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:46: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x767d6bf4da90>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 201 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 2, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -36,7 +36,7 @@
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
-    elif log_format == "auto":
+    elif log_format < "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
         if is_running_in_kubernetes():
             return structlog.processors.JSONRenderer()
........................................................................ [ 15%]
........................................................................ [ 30%]
...........................................................F
=================================== FAILURES ===================================
_______________________ test_get_log_renderer_auto_local _______________________

    def test_get_log_renderer_auto_local():
        """Test auto renderer in local environment."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "auto"
            with patch("stampbot.logger.is_running_in_kubernetes", return_value=False):
                from stampbot.logger import _get_log_renderer
    
                renderer = _get_log_renderer()
>               assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E               AssertionError: assert False
E                +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7247ef060b00>, <class 'structlog.dev.ConsoleRenderer'>)
E                +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E                +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:68: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_local - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7247ef060b00>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 203 passed, 3 deselected, 1 warning in 1.52s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -32,7 +32,7 @@
     """
     log_format = settings.log_format.lower()
 
-    if log_format == "json":
+    if log_format <= "json":
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................................................F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________

    def test_get_log_renderer_console():
        """Test console renderer selection."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "console"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7efbfcac9a90>, <class 'structlog.dev.ConsoleRenderer'>)
E            +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E            +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:46: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7efbfcac9a90>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 201 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 1, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -34,7 +34,7 @@
 
     if log_format == "json":
         return structlog.processors.JSONRenderer()
-    elif log_format == "console":
+    elif log_format <= "console":
         return structlog.dev.ConsoleRenderer(colors=True)
     elif log_format == "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................................................F
=================================== FAILURES ===================================
____________________ test_get_log_renderer_auto_kubernetes _____________________

    def test_get_log_renderer_auto_kubernetes():
        """Test auto renderer in Kubernetes environment."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "auto"
            with patch("stampbot.logger.is_running_in_kubernetes", return_value=True):
                from stampbot.logger import _get_log_renderer
    
                renderer = _get_log_renderer()
>               assert isinstance(renderer, structlog.processors.JSONRenderer)
E               AssertionError: assert False
E                +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7c5e3e1288a0>, <class 'structlog.processors.JSONRenderer'>)
E                +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E                +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors

tests/test_logger.py:57: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_kubernetes - AssertionError: assert False
 +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7c5e3e1288a0>, <class 'structlog.processors.JSONRenderer'>)
 +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
 +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 202 passed, 3 deselected, 1 warning in 1.54s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 2, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -36,7 +36,7 @@
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
-    elif log_format == "auto":
+    elif log_format <= "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
         if is_running_in_kubernetes():
             return structlog.processors.JSONRenderer()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -32,7 +32,7 @@
     """
     log_format = settings.log_format.lower()
 
-    if log_format == "json":
+    if log_format > "json":
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 1, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -34,7 +34,7 @@
 
     if log_format == "json":
         return structlog.processors.JSONRenderer()
-    elif log_format == "console":
+    elif log_format > "console":
         return structlog.dev.ConsoleRenderer(colors=True)
     elif log_format == "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................................................F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________

    def test_get_log_renderer_console():
        """Test console renderer selection."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "console"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7daa19b65a90>, <class 'structlog.dev.ConsoleRenderer'>)
E            +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E            +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:46: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7daa19b65a90>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 201 passed, 3 deselected, 1 warning in 1.54s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 2, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -36,7 +36,7 @@
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
-    elif log_format == "auto":
+    elif log_format > "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
         if is_running_in_kubernetes():
             return structlog.processors.JSONRenderer()
........................................................................ [ 15%]
........................................................................ [ 30%]
...........................................................F
=================================== FAILURES ===================================
_______________________ test_get_log_renderer_auto_local _______________________

    def test_get_log_renderer_auto_local():
        """Test auto renderer in local environment."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "auto"
            with patch("stampbot.logger.is_running_in_kubernetes", return_value=False):
                from stampbot.logger import _get_log_renderer
    
                renderer = _get_log_renderer()
>               assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E               AssertionError: assert False
E                +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x71e834e7cb00>, <class 'structlog.dev.ConsoleRenderer'>)
E                +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E                +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:68: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_local - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x71e834e7cb00>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 203 passed, 3 deselected, 1 warning in 1.51s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -32,7 +32,7 @@
     """
     log_format = settings.log_format.lower()
 
-    if log_format == "json":
+    if log_format >= "json":
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 1, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -34,7 +34,7 @@
 
     if log_format == "json":
         return structlog.processors.JSONRenderer()
-    elif log_format == "console":
+    elif log_format >= "console":
         return structlog.dev.ConsoleRenderer(colors=True)
     elif log_format == "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................................F
=================================== FAILURES ===================================
_____________________ test_get_log_renderer_unknown_format _____________________

    def test_get_log_renderer_unknown_format():
        """Test fallback to JSON for unknown format."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "unknown_format"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.processors.JSONRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x73399bb589d0>, <class 'structlog.processors.JSONRenderer'>)
E            +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E            +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors

tests/test_logger.py:78: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_unknown_format - AssertionError: assert False
 +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x73399bb589d0>, <class 'structlog.processors.JSONRenderer'>)
 +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
 +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 204 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 2, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -36,7 +36,7 @@
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
-    elif log_format == "auto":
+    elif log_format >= "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
         if is_running_in_kubernetes():
             return structlog.processors.JSONRenderer()
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................................F
=================================== FAILURES ===================================
_____________________ test_get_log_renderer_unknown_format _____________________

    def test_get_log_renderer_unknown_format():
        """Test fallback to JSON for unknown format."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "unknown_format"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.processors.JSONRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7da7876389d0>, <class 'structlog.processors.JSONRenderer'>)
E            +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E            +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors

tests/test_logger.py:78: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_unknown_format - AssertionError: assert False
 +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7da7876389d0>, <class 'structlog.processors.JSONRenderer'>)
 +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
 +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 204 passed, 3 deselected, 1 warning in 1.56s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -32,7 +32,7 @@
     """
     log_format = settings.log_format.lower()
 
-    if log_format == "json":
+    if log_format is "json":
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/logger.py:35
  /home/runner/work/stampbot/stampbot/stampbot/logger.py:35: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if log_format is "json":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.42s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 1, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -34,7 +34,7 @@
 
     if log_format == "json":
         return structlog.processors.JSONRenderer()
-    elif log_format == "console":
+    elif log_format is "console":
         return structlog.dev.ConsoleRenderer(colors=True)
     elif log_format == "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................................................F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________

    def test_get_log_renderer_console():
        """Test console renderer selection."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "console"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7c001732d810>, <class 'structlog.dev.ConsoleRenderer'>)
E            +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E            +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:46: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/logger.py:37
  /home/runner/work/stampbot/stampbot/stampbot/logger.py:37: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    elif log_format is "console":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7c001732d810>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 201 passed, 3 deselected, 2 warnings in 1.56s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 2, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -36,7 +36,7 @@
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
-    elif log_format == "auto":
+    elif log_format is "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
         if is_running_in_kubernetes():
             return structlog.processors.JSONRenderer()
........................................................................ [ 15%]
........................................................................ [ 30%]
...........................................................F
=================================== FAILURES ===================================
_______________________ test_get_log_renderer_auto_local _______________________

    def test_get_log_renderer_auto_local():
        """Test auto renderer in local environment."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "auto"
            with patch("stampbot.logger.is_running_in_kubernetes", return_value=False):
                from stampbot.logger import _get_log_renderer
    
                renderer = _get_log_renderer()
>               assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E               AssertionError: assert False
E                +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x735ef9604b00>, <class 'structlog.dev.ConsoleRenderer'>)
E                +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E                +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:68: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/logger.py:39
  /home/runner/work/stampbot/stampbot/stampbot/logger.py:39: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    elif log_format is "auto":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_local - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x735ef9604b00>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 203 passed, 3 deselected, 2 warnings in 1.51s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -32,7 +32,7 @@
     """
     log_format = settings.log_format.lower()
 
-    if log_format == "json":
+    if log_format is not "json":
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................................................F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________

    def test_get_log_renderer_console():
        """Test console renderer selection."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "console"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7d1bfab7e350>, <class 'structlog.dev.ConsoleRenderer'>)
E            +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E            +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:46: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/logger.py:35
  /home/runner/work/stampbot/stampbot/stampbot/logger.py:35: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if log_format is not "json":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7d1bfab7e350>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 201 passed, 3 deselected, 2 warnings in 1.52s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 1, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -34,7 +34,7 @@
 
     if log_format == "json":
         return structlog.processors.JSONRenderer()
-    elif log_format == "console":
+    elif log_format is not "console":
         return structlog.dev.ConsoleRenderer(colors=True)
     elif log_format == "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................................................F
=================================== FAILURES ===================================
____________________ test_get_log_renderer_auto_kubernetes _____________________

    def test_get_log_renderer_auto_kubernetes():
        """Test auto renderer in Kubernetes environment."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "auto"
            with patch("stampbot.logger.is_running_in_kubernetes", return_value=True):
                from stampbot.logger import _get_log_renderer
    
                renderer = _get_log_renderer()
>               assert isinstance(renderer, structlog.processors.JSONRenderer)
E               AssertionError: assert False
E                +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x789c59fd88a0>, <class 'structlog.processors.JSONRenderer'>)
E                +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E                +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors

tests/test_logger.py:57: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/logger.py:37
  /home/runner/work/stampbot/stampbot/stampbot/logger.py:37: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    elif log_format is not "console":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_kubernetes - AssertionError: assert False
 +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x789c59fd88a0>, <class 'structlog.processors.JSONRenderer'>)
 +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
 +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 202 passed, 3 deselected, 2 warnings in 1.55s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 2, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -36,7 +36,7 @@
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
-    elif log_format == "auto":
+    elif log_format is not "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
         if is_running_in_kubernetes():
             return structlog.processors.JSONRenderer()
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................................F
=================================== FAILURES ===================================
_____________________ test_get_log_renderer_unknown_format _____________________

    def test_get_log_renderer_unknown_format():
        """Test fallback to JSON for unknown format."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "unknown_format"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.processors.JSONRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7242d6d089d0>, <class 'structlog.processors.JSONRenderer'>)
E            +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E            +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors

tests/test_logger.py:78: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/logger.py:39
  /home/runner/work/stampbot/stampbot/stampbot/logger.py:39: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    elif log_format is not "auto":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_unknown_format - AssertionError: assert False
 +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x7242d6d089d0>, <class 'structlog.processors.JSONRenderer'>)
 +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
 +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 204 passed, 3 deselected, 2 warnings in 1.57s
operator: core/AddNot, occurrence: 0, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -32,7 +32,7 @@
     """
     log_format = settings.log_format.lower()
 
-    if log_format == "json":
+    if not log_format == "json":
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
         return structlog.dev.ConsoleRenderer(colors=True)
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................................................F
=================================== FAILURES ===================================
________________________ test_get_log_renderer_console _________________________

    def test_get_log_renderer_console():
        """Test console renderer selection."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "console"
            from stampbot.logger import _get_log_renderer
    
            renderer = _get_log_renderer()
>           assert isinstance(renderer, structlog.dev.ConsoleRenderer)
E           AssertionError: assert False
E            +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7029640d5a90>, <class 'structlog.dev.ConsoleRenderer'>)
E            +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
E            +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev

tests/test_logger.py:46: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_console - AssertionError: assert False
 +  where False = isinstance(<structlog.processors.JSONRenderer object at 0x7029640d5a90>, <class 'structlog.dev.ConsoleRenderer'>)
 +    where <class 'structlog.dev.ConsoleRenderer'> = <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'>.ConsoleRenderer
 +      where <module 'structlog.dev' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/dev.py'> = structlog.dev
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 201 passed, 3 deselected, 1 warning in 1.60s
operator: core/AddNot, occurrence: 1, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -38,7 +38,7 @@
         return structlog.dev.ConsoleRenderer(colors=True)
     elif log_format == "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
-        if is_running_in_kubernetes():
+        if not is_running_in_kubernetes():
             return structlog.processors.JSONRenderer()
         else:
             return structlog.dev.ConsoleRenderer(colors=True)
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................................................F
=================================== FAILURES ===================================
____________________ test_get_log_renderer_auto_kubernetes _____________________

    def test_get_log_renderer_auto_kubernetes():
        """Test auto renderer in Kubernetes environment."""
        with patch("stampbot.logger.settings") as mock_settings:
            mock_settings.log_format = "auto"
            with patch("stampbot.logger.is_running_in_kubernetes", return_value=True):
                from stampbot.logger import _get_log_renderer
    
                renderer = _get_log_renderer()
>               assert isinstance(renderer, structlog.processors.JSONRenderer)
E               AssertionError: assert False
E                +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x768e695c60d0>, <class 'structlog.processors.JSONRenderer'>)
E                +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
E                +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors

tests/test_logger.py:57: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_logger.py::test_get_log_renderer_auto_kubernetes - AssertionError: assert False
 +  where False = isinstance(<structlog.dev.ConsoleRenderer object at 0x768e695c60d0>, <class 'structlog.processors.JSONRenderer'>)
 +    where <class 'structlog.processors.JSONRenderer'> = <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'>.JSONRenderer
 +      where <module 'structlog.processors' from '/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/structlog/processors.py'> = structlog.processors
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 202 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceTrueWithFalse, occurrence: 0, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -35,7 +35,7 @@
     if log_format == "json":
         return structlog.processors.JSONRenderer()
     elif log_format == "console":
-        return structlog.dev.ConsoleRenderer(colors=True)
+        return structlog.dev.ConsoleRenderer(colors=False)
     elif log_format == "auto":
         # Auto-detect: use JSON in Kubernetes, console otherwise
         if is_running_in_kubernetes():
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceTrueWithFalse, occurrence: 1, definition_name: _get_log_renderer
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -41,7 +41,7 @@
         if is_running_in_kubernetes():
             return structlog.processors.JSONRenderer()
         else:
-            return structlog.dev.ConsoleRenderer(colors=True)
+            return structlog.dev.ConsoleRenderer(colors=False)
     else:
         # Unknown format, default to JSON for safety
         return structlog.processors.JSONRenderer()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceTrueWithFalse, occurrence: 2, definition_name: configure_logging
--- mutation diff ---
--- a/stampbot/logger.py
+++ b/stampbot/logger.py
@@ -80,7 +80,7 @@
         context_class=dict,
         # Route structlog through stdlib so all output shares one handler.
         logger_factory=structlog.stdlib.LoggerFactory(),
-        cache_logger_on_first_use=True,
+        cache_logger_on_first_use=False,
     )
 
     # ProcessorFormatter ensures uvicorn access logs and every other stdlib
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=( 1.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.73s
operator: core/NumberReplacer, occurrence: 1, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=( -0.995, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [-0.995, 0.01....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
E             
E             At index 0 diff: -0.995 != 0.005
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     0.005,
E             ?       ^^
E             +     -0.995,
E             ?     +  ^^
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [-0.995, 0.01....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
  
  At index 0 diff: -0.995 != 0.005
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     0.005,
  ?       ^^
  +     -0.995,
  ?     +  ^^
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 2, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 1.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 3, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, -0.99, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/NumberReplacer, occurrence: 4, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 1.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 5, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, -0.975, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.73s
operator: core/NumberReplacer, occurrence: 6, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 1.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 7, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, -0.95, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.73s
operator: core/NumberReplacer, occurrence: 8, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 1.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 9, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, -0.9, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 10, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 1.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.72s
operator: core/NumberReplacer, occurrence: 11, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, -0.75, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 12, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 1.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/NumberReplacer, occurrence: 13, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, -0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 14, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 2.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
E             
E             At index 7 diff: 2.0 != 1.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.005,
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E             -     1.0,
E             ?     ^
E             +     2.0,
E             ?     ^
E                   2.5,
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
  
  At index 7 diff: 2.0 != 1.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.005,
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
  -     1.0,
  ?     ^
  +     2.0,
  ?     ^
        2.5,
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.26s
operator: core/NumberReplacer, occurrence: 15, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 0.0, 2.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:40: in <module>
    http_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/NumberReplacer, occurrence: 16, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 3.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
E             
E             At index 8 diff: 3.5 != 2.5
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.005,
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E             -     2.5,
E             ?     ^
E             +     3.5,
E             ?     ^
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
  
  At index 8 diff: 3.5 != 2.5
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.005,
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
  -     2.5,
  ?     ^
  +     3.5,
  ?     ^
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.26s
operator: core/NumberReplacer, occurrence: 17, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 1.5, 5.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
E             
E             At index 8 diff: 1.5 != 2.5
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.005,
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E             -     2.5,
E             ?     ^
E             +     1.5,
E             ?     ^
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
  
  At index 8 diff: 1.5 != 2.5
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.005,
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
  -     2.5,
  ?     ^
  +     1.5,
  ?     ^
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.24s
operator: core/NumberReplacer, occurrence: 18, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 6.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
E             
E             At index 9 diff: 6.0 != 5.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.005,
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E             -     5.0,
E             ?     ^
E             +     6.0,
E             ?     ^
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
  
  At index 9 diff: 6.0 != 5.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.005,
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
  -     5.0,
  ?     ^
  +     6.0,
  ?     ^
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 19, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 4.0, 10.0),
 )
 
 http_request_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
E             
E             At index 9 diff: 4.0 != 5.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.005,
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E             -     5.0,
E             ?     ^
E             +     4.0,
E             ?     ^
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
  
  At index 9 diff: 4.0 != 5.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.005,
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
  -     5.0,
  ?     ^
  +     4.0,
  ?     ^
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 20, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 11.0),
 )
 
 http_request_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
E             
E             At index 10 diff: 11.0 != 10.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.005,
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E             -     10.0,
E             ?      ^
E             +     11.0,
E             ?      ^
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
  
  At index 10 diff: 11.0 != 10.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.005,
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
  -     10.0,
  ?      ^
  +     11.0,
  ?      ^
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 21, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -41,7 +41,7 @@
     "stampbot_http_request_duration_seconds",
     "HTTP request duration in seconds",
     ["method", "endpoint"],
-    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 9.0),
 )
 
 http_request_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
E             
E             At index 10 diff: 9.0 != 10.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.005,
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E             -     10.0,
E             ?     ^^
E             +     9.0,
E             ?     ^
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.005, 0.01,....1, 0.25, ...] == [0.005, 0.01,....1, 0.25, ...]
  
  At index 10 diff: 9.0 != 10.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.005,
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
  -     10.0,
  ?     ^^
  +     9.0,
  ?     ^
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.25s
operator: core/NumberReplacer, occurrence: 22, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=( 101, 500, 1000, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [101.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 0 diff: 101.0 != 100
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             +     101.0,
E             ?       ++
E             -     500,
E             -     1000,
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [101.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 0 diff: 101.0 != 100
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  +     101.0,
  ?       ++
  -     500,
  -     1000,
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.25s
operator: core/NumberReplacer, occurrence: 23, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=( 99, 500, 1000, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [99.0, 500.0,... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 0 diff: 99.0 != 100
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             +     99.0,
E             -     100,
E             -     500,
E             -     1000,
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [99.0, 500.0,... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 0 diff: 99.0 != 100
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  +     99.0,
  -     100,
  -     500,
  -     1000,
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 24, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 501, 1000, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 501.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 1 diff: 501.0 != 500
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             ?       ^
E             +     501.0,
E             ?       ^^
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 501.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 1 diff: 501.0 != 500
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  ?       ^
  +     501.0,
  ?       ^^
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 25, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 499, 1000, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 499.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 1 diff: 499.0 != 500
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     499.0,
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 499.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 1 diff: 499.0 != 500
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     499.0,
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 26, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1001, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 2 diff: 1001.0 != 1000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             ?        ^
E             +     1001.0,
E             ?        ^^
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 2 diff: 1001.0 != 1000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  ?        ^
  +     1001.0,
  ?        ^^
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 27, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 999, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 2 diff: 999.0 != 1000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     999.0,
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 2 diff: 999.0 != 1000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     999.0,
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 28, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5001, 10000, 50000, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 3 diff: 5001.0 != 5000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             ?        ^
E             +     5001.0,
E             ?        ^^
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 3 diff: 5001.0 != 5000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  ?        ^
  +     5001.0,
  ?        ^^
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 29, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 4999, 10000, 50000, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 3 diff: 4999.0 != 5000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     4999.0,
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 3 diff: 4999.0 != 5000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     4999.0,
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.26s
operator: core/NumberReplacer, occurrence: 30, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10001, 50000, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 4 diff: 10001.0 != 10000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             ?         ^
E             +     10001.0,
E             ?         ^^
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 4 diff: 10001.0 != 10000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  ?         ^
  +     10001.0,
  ?         ^^
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 31, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 9999, 50000, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 4 diff: 9999.0 != 10000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     9999.0,
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 4 diff: 9999.0 != 10000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     9999.0,
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 32, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 50001, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50001.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 5 diff: 50001.0 != 50000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             ?         ^
E             +     50001.0,
E             ?         ^^
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50001.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 5 diff: 50001.0 != 50000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  ?         ^
  +     50001.0,
  ?         ^^
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 33, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 49999, 100000, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 49999.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 5 diff: 49999.0 != 50000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             +     49999.0,
E             -     500000,
E             ?     ^
E             +     100000.0,
E             ?     ^     ++
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 49999.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 5 diff: 49999.0 != 50000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  +     49999.0,
  -     500000,
  ?     ^
  +     100000.0,
  ?     ^     ++
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.31s
operator: core/NumberReplacer, occurrence: 34, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 50000, 100001, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 6 diff: 100001.0 != 100000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100001.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 6 diff: 100001.0 != 100000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100001.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 35, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 50000, 99999, 500000),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 6 diff: 99999.0 != 100000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     99999.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 6 diff: 99999.0 != 100000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     99999.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 36, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500001),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 7 diff: 500001.0 != 500000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500001.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 7 diff: 500001.0 != 500000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500001.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 37, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -48,7 +48,7 @@
     "stampbot_http_request_size_bytes",
     "HTTP request body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 499999),
 )
 
 http_response_size_bytes = Histogram(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 7 diff: 499999.0 != 500000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     499999.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 7 diff: 499999.0 != 500000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     499999.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 38, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=( 101, 500, 1000, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [101.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 0 diff: 101.0 != 100
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             +     101.0,
E             ?       ++
E             -     500,
E             -     1000,
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [101.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 0 diff: 101.0 != 100
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  +     101.0,
  ?       ++
  -     500,
  -     1000,
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 39, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=( 99, 500, 1000, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [99.0, 500.0,... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 0 diff: 99.0 != 100
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             +     99.0,
E             -     100,
E             -     500,
E             -     1000,
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [99.0, 500.0,... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 0 diff: 99.0 != 100
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  +     99.0,
  -     100,
  -     500,
  -     1000,
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.25s
operator: core/NumberReplacer, occurrence: 40, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 501, 1000, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 501.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 1 diff: 501.0 != 500
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             ?       ^
E             +     501.0,
E             ?       ^^
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 501.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 1 diff: 501.0 != 500
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  ?       ^
  +     501.0,
  ?       ^^
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 41, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 499, 1000, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 499.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 1 diff: 499.0 != 500
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     499.0,
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 499.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 1 diff: 499.0 != 500
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     499.0,
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.26s
operator: core/NumberReplacer, occurrence: 42, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1001, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 2 diff: 1001.0 != 1000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             ?        ^
E             +     1001.0,
E             ?        ^^
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 2 diff: 1001.0 != 1000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  ?        ^
  +     1001.0,
  ?        ^^
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.25s
operator: core/NumberReplacer, occurrence: 43, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 999, 5000, 10000, 50000, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 2 diff: 999.0 != 1000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     999.0,
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 2 diff: 999.0 != 1000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     999.0,
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.30s
operator: core/NumberReplacer, occurrence: 44, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5001, 10000, 50000, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 3 diff: 5001.0 != 5000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             ?        ^
E             +     5001.0,
E             ?        ^^
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 3 diff: 5001.0 != 5000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  ?        ^
  +     5001.0,
  ?        ^^
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 45, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 4999, 10000, 50000, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 3 diff: 4999.0 != 5000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     4999.0,
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 3 diff: 4999.0 != 5000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     4999.0,
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.24s
operator: core/NumberReplacer, occurrence: 46, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10001, 50000, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 4 diff: 10001.0 != 10000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             ?         ^
E             +     10001.0,
E             ?         ^^
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 4 diff: 10001.0 != 10000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  ?         ^
  +     10001.0,
  ?         ^^
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 47, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 9999, 50000, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 4 diff: 9999.0 != 10000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     9999.0,
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 4 diff: 9999.0 != 10000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     9999.0,
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 48, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 50001, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50001.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 5 diff: 50001.0 != 50000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             ?         ^
E             +     50001.0,
E             ?         ^^
E             +     100000.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50001.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 5 diff: 50001.0 != 50000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  ?         ^
  +     50001.0,
  ?         ^^
  +     100000.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 49, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 49999, 100000, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 49999.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 5 diff: 49999.0 != 50000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             +     49999.0,
E             -     500000,
E             ?     ^
E             +     100000.0,
E             ?     ^     ++
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 49999.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 5 diff: 49999.0 != 50000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  +     49999.0,
  -     500000,
  ?     ^
  +     100000.0,
  ?     ^     ++
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 50, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 50000, 100001, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 6 diff: 100001.0 != 100000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100001.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 6 diff: 100001.0 != 100000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100001.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 51, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 50000, 99999, 500000),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 6 diff: 99999.0 != 100000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     99999.0,
E             +     500000.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 6 diff: 99999.0 != 100000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     99999.0,
  +     500000.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 52, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500001),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 7 diff: 500001.0 != 500000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     500001.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 7 diff: 500001.0 != 500000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     500001.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 53, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -55,7 +55,7 @@
     "stampbot_http_response_size_bytes",
     "HTTP response body size in bytes",
     ["method", "endpoint"],
-    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
+    buckets=(100, 500, 1000, 5000, 10000, 50000, 100000, 499999),
 )
 
 http_requests_in_progress = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
E             
E             At index 7 diff: 499999.0 != 500000
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     100,
E             -     500,
E             -     1000,
E             +     100.0,
E             ?        +
E             -     5000,
E             +     500.0,
E             ?        +
E             -     10000,
E             +     1000.0,
E             ?         +
E             -     50000,
E             +     5000.0,
E             ?         +
E             -     100000,
E             +     10000.0,
E             ?          +
E             -     500000,
E             +     50000.0,
E             ?          +
E             +     100000.0,
E             +     499999.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [100.0, 500.0... 50000.0, ...] == [100, 500, 10...0, 50000, ...]
  
  At index 7 diff: 499999.0 != 500000
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     100,
  -     500,
  -     1000,
  +     100.0,
  ?        +
  -     5000,
  +     500.0,
  ?        +
  -     10000,
  +     1000.0,
  ?         +
  -     50000,
  +     5000.0,
  ?         +
  -     100000,
  +     10000.0,
  ?          +
  -     500000,
  +     50000.0,
  ?          +
  +     100000.0,
  +     499999.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.31s
operator: core/NumberReplacer, occurrence: 54, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=( 1.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 55, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=( -0.99, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [-0.99, 0.025....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
E             
E             At index 0 diff: -0.99 != 0.01
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     0.01,
E             +     -0.99,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [-0.99, 0.025....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
  
  At index 0 diff: -0.99 != 0.01
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     0.01,
  +     -0.99,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 56, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 1.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 57, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, -0.975, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 58, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 1.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.77s
operator: core/NumberReplacer, occurrence: 59, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, -0.95, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 60, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 1.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.73s
operator: core/NumberReplacer, occurrence: 61, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, -0.9, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 62, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, 1.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 63, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, -0.75, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 64, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 1.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 65, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, -0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.73s
operator: core/NumberReplacer, occurrence: 66, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 2.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
E             
E             At index 6 diff: 2.0 != 1.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E             -     1.0,
E             ?     ^
E             +     2.0,
E             ?     ^
E                   2.5,
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
  
  At index 6 diff: 2.0 != 1.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
  -     1.0,
  ?     ^
  +     2.0,
  ?     ^
        2.5,
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 67, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 0.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:83: in <module>
    webhook_processing_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 68, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 3.5, 5.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
E             
E             At index 7 diff: 3.5 != 2.5
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E             -     2.5,
E             ?     ^
E             +     3.5,
E             ?     ^
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
  
  At index 7 diff: 3.5 != 2.5
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
  -     2.5,
  ?     ^
  +     3.5,
  ?     ^
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 69, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 1.5, 5.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
E             
E             At index 7 diff: 1.5 != 2.5
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E             -     2.5,
E             ?     ^
E             +     1.5,
E             ?     ^
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
  
  At index 7 diff: 1.5 != 2.5
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
  -     2.5,
  ?     ^
  +     1.5,
  ?     ^
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 70, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 6.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
E             
E             At index 8 diff: 6.0 != 5.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E             -     5.0,
E             ?     ^
E             +     6.0,
E             ?     ^
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
  
  At index 8 diff: 6.0 != 5.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
  -     5.0,
  ?     ^
  +     6.0,
  ?     ^
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.30s
operator: core/NumberReplacer, occurrence: 71, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 4.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
E             
E             At index 8 diff: 4.0 != 5.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E             -     5.0,
E             ?     ^
E             +     4.0,
E             ?     ^
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
  
  At index 8 diff: 4.0 != 5.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
  -     5.0,
  ?     ^
  +     4.0,
  ?     ^
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.31s
operator: core/NumberReplacer, occurrence: 72, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 11.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
E             
E             At index 9 diff: 11.0 != 10.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E             -     10.0,
E             ?      ^
E             +     11.0,
E             ?      ^
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
  
  At index 9 diff: 11.0 != 10.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
  -     10.0,
  ?      ^
  +     11.0,
  ?      ^
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 73, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -84,7 +84,7 @@
     "stampbot_webhook_processing_duration_seconds",
     "Webhook event processing duration in seconds",
     ["event_type"],
-    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 9.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
E             
E             At index 9 diff: 9.0 != 10.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.01,
E                   0.025,
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E             -     10.0,
E             ?     ^^
E             +     9.0,
E             ?     ^
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.01, 0.025,....25, 0.5, ...] == [0.01, 0.025,....25, 0.5, ...]
  
  At index 9 diff: 9.0 != 10.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.01,
        0.025,
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
  -     10.0,
  ?     ^^
  +     9.0,
  ?     ^
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 74, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=( 1.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:100: in <module>
    pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 75, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=( -0.9, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [-0.9, 0.25, ...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 0 diff: -0.9 != 0.1
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     0.1,
E             ?       ^
E             +     -0.9,
E             ?     +  ^
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [-0.9, 0.25, ...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 0 diff: -0.9 != 0.1
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     0.1,
  ?       ^
  +     -0.9,
  ?     +  ^
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.31s
operator: core/NumberReplacer, occurrence: 76, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 1.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:100: in <module>
    pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/NumberReplacer, occurrence: 77, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, -0.75, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:100: in <module>
    pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 78, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 1.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:100: in <module>
    pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 79, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, -0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:100: in <module>
    pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 80, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 2.0, 2.5, 5.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 3 diff: 2.0 != 1.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E             -     1.0,
E             ?     ^
E             +     2.0,
E             ?     ^
E                   2.5,
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 3 diff: 2.0 != 1.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
  -     1.0,
  ?     ^
  +     2.0,
  ?     ^
        2.5,
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 81, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 0.0, 2.5, 5.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:100: in <module>
    pr_approval_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/NumberReplacer, occurrence: 82, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 3.5, 5.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...3.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 4 diff: 3.5 != 2.5
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E             -     2.5,
E             ?     ^
E             +     3.5,
E             ?     ^
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...3.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 4 diff: 3.5 != 2.5
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
  -     2.5,
  ?     ^
  +     3.5,
  ?     ^
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.25s
operator: core/NumberReplacer, occurrence: 83, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 1.5, 5.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...1.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 4 diff: 1.5 != 2.5
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E             -     2.5,
E             ?     ^
E             +     1.5,
E             ?     ^
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...1.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 4 diff: 1.5 != 2.5
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
  -     2.5,
  ?     ^
  +     1.5,
  ?     ^
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.26s
operator: core/NumberReplacer, occurrence: 84, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 6.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...2.5, 6.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 5 diff: 6.0 != 5.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E             -     5.0,
E             ?     ^
E             +     6.0,
E             ?     ^
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...2.5, 6.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 5 diff: 6.0 != 5.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
  -     5.0,
  ?     ^
  +     6.0,
  ?     ^
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 85, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 4.0, 10.0),
 )
 
 pr_dismissals_total = Counter(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...2.5, 4.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 5 diff: 4.0 != 5.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E             -     5.0,
E             ?     ^
E             +     4.0,
E             ?     ^
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...2.5, 4.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 5 diff: 4.0 != 5.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
  -     5.0,
  ?     ^
  +     4.0,
  ?     ^
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.30s
operator: core/NumberReplacer, occurrence: 86, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 11.0),
 )
 
 pr_dismissals_total = Counter(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 6 diff: 11.0 != 10.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E             -     10.0,
E             ?      ^
E             +     11.0,
E             ?      ^
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 6 diff: 11.0 != 10.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
  -     10.0,
  ?      ^
  +     11.0,
  ?      ^
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.30s
operator: core/NumberReplacer, occurrence: 87, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -100,7 +100,7 @@
 pr_approval_duration_seconds = Histogram(
     "stampbot_pr_approval_duration_seconds",
     "PR approval operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 9.0),
 )
 
 pr_dismissals_total = Counter(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 6 diff: 9.0 != 10.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E             -     10.0,
E             ?     ^^
E             +     9.0,
E             ?     ^
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 6 diff: 9.0 != 10.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
  -     10.0,
  ?     ^^
  +     9.0,
  ?     ^
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.30s
operator: core/NumberReplacer, occurrence: 88, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=( 1.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:115: in <module>
    pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 89, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=( -0.9, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [-0.9, 0.25, ...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 0 diff: -0.9 != 0.1
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     0.1,
E             ?       ^
E             +     -0.9,
E             ?     +  ^
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [-0.9, 0.25, ...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 0 diff: -0.9 != 0.1
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     0.1,
  ?       ^
  +     -0.9,
  ?     +  ^
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 90, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 1.25, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:115: in <module>
    pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 91, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, -0.75, 0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:115: in <module>
    pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.77s
operator: core/NumberReplacer, occurrence: 92, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 1.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:115: in <module>
    pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.77s
operator: core/NumberReplacer, occurrence: 93, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, -0.5, 1.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:115: in <module>
    pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.73s
operator: core/NumberReplacer, occurrence: 94, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 2.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 3 diff: 2.0 != 1.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E             -     1.0,
E             ?     ^
E             +     2.0,
E             ?     ^
E                   2.5,
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 3 diff: 2.0 != 1.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
  -     1.0,
  ?     ^
  +     2.0,
  ?     ^
        2.5,
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.30s
operator: core/NumberReplacer, occurrence: 95, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 0.0, 2.5, 5.0, 10.0),
 )
 
 # =============================================================================
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:115: in <module>
    pr_dismissal_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 96, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 3.5, 5.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...3.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 4 diff: 3.5 != 2.5
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E             -     2.5,
E             ?     ^
E             +     3.5,
E             ?     ^
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...3.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 4 diff: 3.5 != 2.5
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
  -     2.5,
  ?     ^
  +     3.5,
  ?     ^
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.30s
operator: core/NumberReplacer, occurrence: 97, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 1.5, 5.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...1.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 4 diff: 1.5 != 2.5
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E             -     2.5,
E             ?     ^
E             +     1.5,
E             ?     ^
E                   5.0,
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...1.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 4 diff: 1.5 != 2.5
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
  -     2.5,
  ?     ^
  +     1.5,
  ?     ^
        5.0,
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 98, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 6.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...2.5, 6.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 5 diff: 6.0 != 5.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E             -     5.0,
E             ?     ^
E             +     6.0,
E             ?     ^
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...2.5, 6.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 5 diff: 6.0 != 5.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
  -     5.0,
  ?     ^
  +     6.0,
  ?     ^
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 99, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 4.0, 10.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...2.5, 4.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 5 diff: 4.0 != 5.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E             -     5.0,
E             ?     ^
E             +     4.0,
E             ?     ^
E                   10.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...2.5, 4.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 5 diff: 4.0 != 5.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
  -     5.0,
  ?     ^
  +     4.0,
  ?     ^
        10.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.35s
operator: core/NumberReplacer, occurrence: 100, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 11.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 6 diff: 11.0 != 10.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E             -     10.0,
E             ?      ^
E             +     11.0,
E             ?      ^
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 6 diff: 11.0 != 10.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
  -     10.0,
  ?      ^
  +     11.0,
  ?      ^
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 101, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -115,7 +115,7 @@
 pr_dismissal_duration_seconds = Histogram(
     "stampbot_pr_dismissal_duration_seconds",
     "PR dismissal operation duration in seconds",
-    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
+    buckets=(0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 9.0),
 )
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
E             
E             At index 6 diff: 9.0 != 10.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E             -     10.0,
E             ?     ^^
E             +     9.0,
E             ?     ^
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.1, 0.25, 0...2.5, 5.0, ...] == [0.1, 0.25, 0...2.5, 5.0, ...]
  
  At index 6 diff: 9.0 != 10.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
  -     10.0,
  ?     ^^
  +     9.0,
  ?     ^
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 102, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=( 1.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:144: in <module>
    github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 103, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=( -0.95, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [-0.95, 0.1, ...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
E             
E             At index 0 diff: -0.95 != 0.05
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E             -     0.05,
E             ?       ^
E             +     -0.95,
E             ?     +  ^
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E                   10.0,
E                   30.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [-0.95, 0.1, ...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
  
  At index 0 diff: -0.95 != 0.05
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     0.05,
  ?       ^
  +     -0.95,
  ?     +  ^
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
        10.0,
        30.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 104, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 1.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:144: in <module>
    github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.73s
operator: core/NumberReplacer, occurrence: 105, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, -0.9, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:144: in <module>
    github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/NumberReplacer, occurrence: 106, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 1.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:144: in <module>
    github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/NumberReplacer, occurrence: 107, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, -0.75, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:144: in <module>
    github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 108, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, 1.5, 1.0, 2.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:144: in <module>
    github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/NumberReplacer, occurrence: 109, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, -0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:144: in <module>
    github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 110, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, 0.5, 2.0, 2.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.05, 0.1, 0...2.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
E             
E             At index 4 diff: 2.0 != 1.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E             -     1.0,
E             ?     ^
E             +     2.0,
E             ?     ^
E                   2.5,
E                   5.0,
E                   10.0,
E                   30.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.05, 0.1, 0...2.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
  
  At index 4 diff: 2.0 != 1.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.05,
        0.1,
        0.25,
        0.5,
  -     1.0,
  ?     ^
  +     2.0,
  ?     ^
        2.5,
        5.0,
        10.0,
        30.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.26s
operator: core/NumberReplacer, occurrence: 111, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, 0.5, 0.0, 2.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
stampbot/github_client.py:22: in <module>
    from stampbot.installation_auth import InstrumentedInstallationAuth
stampbot/installation_auth.py:12: in <module>
    from stampbot.metrics import github_api_request_duration_seconds, github_api_requests_total
stampbot/metrics.py:144: in <module>
    github_api_request_duration_seconds = Histogram(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:627: in __init__
    self._prepare_buckets(buckets)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:645: in _prepare_buckets
    raise ValueError('Buckets not in sorted order')
E   ValueError: Buckets not in sorted order
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: Buckets not in sorted order
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/NumberReplacer, occurrence: 112, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 3.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.05, 0.1, 0...1.0, 3.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
E             
E             At index 5 diff: 3.5 != 2.5
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E             -     2.5,
E             ?     ^
E             +     3.5,
E             ?     ^
E                   5.0,
E                   10.0,
E                   30.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.05, 0.1, 0...1.0, 3.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
  
  At index 5 diff: 3.5 != 2.5
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
  -     2.5,
  ?     ^
  +     3.5,
  ?     ^
        5.0,
        10.0,
        30.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.30s
operator: core/NumberReplacer, occurrence: 113, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 1.5, 5.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.05, 0.1, 0...1.0, 1.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
E             
E             At index 5 diff: 1.5 != 2.5
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E             -     2.5,
E             ?     ^
E             +     1.5,
E             ?     ^
E                   5.0,
E                   10.0,
E                   30.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.05, 0.1, 0...1.0, 1.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
  
  At index 5 diff: 1.5 != 2.5
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
  -     2.5,
  ?     ^
  +     1.5,
  ?     ^
        5.0,
        10.0,
        30.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 114, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 6.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
E             
E             At index 6 diff: 6.0 != 5.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E             -     5.0,
E             ?     ^
E             +     6.0,
E             ?     ^
E                   10.0,
E                   30.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
  
  At index 6 diff: 6.0 != 5.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
  -     5.0,
  ?     ^
  +     6.0,
  ?     ^
        10.0,
        30.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.30s
operator: core/NumberReplacer, occurrence: 115, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 4.0, 10.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
E             
E             At index 6 diff: 4.0 != 5.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E             -     5.0,
E             ?     ^
E             +     4.0,
E             ?     ^
E                   10.0,
E                   30.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
  
  At index 6 diff: 4.0 != 5.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
  -     5.0,
  ?     ^
  +     4.0,
  ?     ^
        10.0,
        30.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.30s
operator: core/NumberReplacer, occurrence: 116, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 11.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
E             
E             At index 7 diff: 11.0 != 10.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E             -     10.0,
E             ?      ^
E             +     11.0,
E             ?      ^
E                   30.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
  
  At index 7 diff: 11.0 != 10.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
  -     10.0,
  ?      ^
  +     11.0,
  ?      ^
        30.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.30s
operator: core/NumberReplacer, occurrence: 117, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 9.0, 30.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
E             
E             At index 7 diff: 9.0 != 10.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E             -     10.0,
E             ?     ^^
E             +     9.0,
E             ?     ^
E                   30.0,
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
  
  At index 7 diff: 9.0 != 10.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
  -     10.0,
  ?     ^^
  +     9.0,
  ?     ^
        30.0,
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.32s
operator: core/NumberReplacer, occurrence: 118, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 31.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
E             
E             At index 8 diff: 31.0 != 30.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E                   10.0,
E             -     30.0,
E             ?      ^
E             +     31.0,
E             ?      ^
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
  
  At index 8 diff: 31.0 != 30.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
        10.0,
  -     30.0,
  ?      ^
  +     31.0,
  ?      ^
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.33s
operator: core/NumberReplacer, occurrence: 119, definition_name: None
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -145,7 +145,7 @@
     "stampbot_github_api_request_duration_seconds",
     "GitHub API request duration in seconds",
     ["operation"],
-    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
+    buckets=(0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 29.0),
 )
 
 github_api_rate_limit_remaining = Gauge(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................................................................F
=================================== FAILURES ===================================
___________________________ test_histogram_contracts ___________________________

    def test_histogram_contracts():
        """Test histogram names, labels, and bucket boundaries."""
        expected_histograms = {
            "http_request_duration_seconds": {
                "name": "stampbot_http_request_duration_seconds",
                "labels": ("method", "endpoint"),
                "buckets": (0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "http_request_size_bytes": {
                "name": "stampbot_http_request_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "http_response_size_bytes": {
                "name": "stampbot_http_response_size_bytes",
                "labels": ("method", "endpoint"),
                "buckets": (100, 500, 1000, 5000, 10000, 50000, 100000, 500000),
            },
            "webhook_processing_duration_seconds": {
                "name": "stampbot_webhook_processing_duration_seconds",
                "labels": ("event_type",),
                "buckets": (0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_approval_duration_seconds": {
                "name": "stampbot_pr_approval_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "pr_dismissal_duration_seconds": {
                "name": "stampbot_pr_dismissal_duration_seconds",
                "labels": (),
                "buckets": (0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0),
            },
            "github_api_request_duration_seconds": {
                "name": "stampbot_github_api_request_duration_seconds",
                "labels": ("operation",),
                "buckets": (0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0),
            },
        }
    
        for attribute, expected in expected_histograms.items():
            histogram = getattr(metrics, attribute)
    
            assert isinstance(histogram, Histogram)
            assert histogram._name == expected["name"]
            assert histogram._labelnames == expected["labels"]
>           assert histogram._upper_bounds == [*expected["buckets"], math.inf]
E           assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
E             
E             At index 8 diff: 29.0 != 30.0
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   0.05,
E                   0.1,
E                   0.25,
E                   0.5,
E                   1.0,
E                   2.5,
E                   5.0,
E                   10.0,
E             -     30.0,
E             ?     ^^
E             +     29.0,
E             ?     ^^
E                   inf,
E               ]

tests/test_metrics.py:59: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_metrics.py::test_histogram_contracts - assert [0.05, 0.1, 0...1.0, 2.5, ...] == [0.05, 0.1, 0...1.0, 2.5, ...]
  
  At index 8 diff: 29.0 != 30.0
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        0.05,
        0.1,
        0.25,
        0.5,
        1.0,
        2.5,
        5.0,
        10.0,
  -     30.0,
  ?     ^^
  +     29.0,
  ?     ^^
        inf,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 283 passed, 3 deselected, 1 warning in 2.26s
operator: core/NumberReplacer, occurrence: 120, definition_name: stop_metrics_server
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -204,7 +204,7 @@
     """
     server.shutdown()
     server.server_close()
-    thread.join(timeout=5)
+    thread.join(timeout= 6)
 
 
 def set_app_info(version: str) -> None:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/NumberReplacer, occurrence: 121, definition_name: stop_metrics_server
--- mutation diff ---
--- a/stampbot/metrics.py
+++ b/stampbot/metrics.py
@@ -204,7 +204,7 @@
     """
     server.shutdown()
     server.server_close()
-    thread.join(timeout=5)
+    thread.join(timeout= 4)
 
 
 def set_app_info(version: str) -> None:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -119,7 +119,7 @@
 
                 org_repo_full_name = None
                 if (
-                    owner_type == "Organization"
+                    owner_type != "Organization"
                     and owner_login
                     and repo_full_name != f"{owner_login}/.github"
                 ):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d5e2877be70>
mock_github_client = <MagicMock name='github_client' id='137843380909264'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='137843380910944'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:37:06.141576Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": "octocat/.github"}, "event": "No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:37:06.142070Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:37:06.142283Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:37:06.142657Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:37:06.142821Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:37:06.141576Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:37:06.142070Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:37:06.142283Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:37:06.142657Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:37:06.142821Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -119,7 +119,7 @@
 
                 org_repo_full_name = None
                 if (
-                    owner_type == "Organization"
+                    owner_type < "Organization"
                     and owner_login
                     and repo_full_name != f"{owner_login}/.github"
                 ):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77d59a4bb150>
mock_github_client = <MagicMock name='github_client' id='131759298006720'>

    @pytest.mark.asyncio
    async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
        """Test org .github stampbot.toml is used when repo config is missing."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["repository"]["full_name"] = "acme/widgets"
        payload["repository"]["default_branch"] = "main"
        payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
        payload["pull_request"]["labels"] = [{"name": "org-approve"}]
        payload["label"] = {"name": "org-approve"}
    
        def get_repo_file_side_effect(
            _installation_id,
            repo_full_name,
            _file_path,
            _ref,
            *,
            missing_repository_is_optional=False,
        ):
            if repo_full_name == "acme/widgets":
                assert missing_repository_is_optional is False
                return None
            if repo_full_name == "acme/.github":
                assert missing_repository_is_optional is True
                return 'approval_labels = ["org-approve"]'
            return None
    
        mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:1178: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:41:31.859449Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "acme/widgets", "org_repo": null}, "event": "No stampbot.toml found in acme/widgets, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:41:31.859867Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:41:31.859449Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'acme/widgets', 'org_repo': None}, 'event': 'No stampbot.toml found in acme/widgets, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:41:31.859867Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 444 passed, 3 deselected, 1 warning in 3.30s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -119,7 +119,7 @@
 
                 org_repo_full_name = None
                 if (
-                    owner_type == "Organization"
+                    owner_type <= "Organization"
                     and owner_login
                     and repo_full_name != f"{owner_login}/.github"
                 ):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
..............................F
=================================== FAILURES ===================================
____________________ test_pull_request_review_comment_event ____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x78cb23cdac10>
mock_github_client = <MagicMock name='github_client' id='132813868756880'>

    @pytest.mark.asyncio
    async def test_pull_request_review_comment_event(webhook_handler, mock_github_client):
        """Test handling pull_request_review_comment event type."""
        payload = {
            "action": "created",
            "pull_request": {
                "number": 42,
            },
            "comment": {
                "body": "@stampbot approve",
                "user": {"login": "testuser"},
            },
            "repository": {
                "full_name": "owner/repo",
            },
            "installation": {"id": 12345},
        }
    
        result = await webhook_handler.handle_event("pull_request_review_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:1487: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request_review_comment", "action": "created"}, "event": "Received pull_request_review_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:19:36.035517Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "owner/repo", "error": "'<=' not supported between instances of 'NoneType' and 'str'"}, "event": "Error loading config from owner/repo: '<=' not supported between instances of 'NoneType' and 'str', disabling automation", "logger": "stampbot.repo_policy", "level": "warning", "timestamp": "2026-11-04T04:19:36.035897Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request_review_comment', 'action': 'created'}, 'event': 'Received pull_request_review_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:19:36.035517Z'}
WARNING  stampbot.repo_policy:repo_policy.py:172 {'extra': {'repo': 'owner/repo', 'error': "'<=' not supported between instances of 'NoneType' and 'str'"}, 'event': "Error loading config from owner/repo: '<=' not supported between instances of 'NoneType' and 'str', disabling automation", 'logger': 'stampbot.repo_policy', 'level': 'warning', 'timestamp': '2026-11-04T04:19:36.035897Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pull_request_review_comment_event - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 462 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -119,7 +119,7 @@
 
                 org_repo_full_name = None
                 if (
-                    owner_type == "Organization"
+                    owner_type > "Organization"
                     and owner_login
                     and repo_full_name != f"{owner_login}/.github"
                 ):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x747b03fc3e70>
mock_github_client = <MagicMock name='github_client' id='128071787918544'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='128071787920224'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:14:37.112723Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": "octocat/.github"}, "event": "No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:14:37.113240Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:14:37.113428Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:14:37.113785Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:14:37.113942Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:14:37.112723Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:14:37.113240Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:14:37.113428Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:14:37.113785Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:14:37.113942Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -119,7 +119,7 @@
 
                 org_repo_full_name = None
                 if (
-                    owner_type == "Organization"
+                    owner_type >= "Organization"
                     and owner_login
                     and repo_full_name != f"{owner_login}/.github"
                 ):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7be9b79ebb60>
mock_github_client = <MagicMock name='github_client' id='136243759330512'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='136243759332192'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:26.046640Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": "octocat/.github"}, "event": "No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:18:26.047164Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:26.047374Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:26.047813Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:26.047987Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:26.046640Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:18:26.047164Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:26.047374Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:26.047813Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:26.047987Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.49s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -119,7 +119,7 @@
 
                 org_repo_full_name = None
                 if (
-                    owner_type == "Organization"
+                    owner_type is "Organization"
                     and owner_login
                     and repo_full_name != f"{owner_login}/.github"
                 ):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/repo_policy.py:122
  /home/runner/work/stampbot/stampbot/stampbot/repo_policy.py:122: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    owner_type is "Organization"

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.36s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -119,7 +119,7 @@
 
                 org_repo_full_name = None
                 if (
-                    owner_type == "Organization"
+                    owner_type is not "Organization"
                     and owner_login
                     and repo_full_name != f"{owner_login}/.github"
                 ):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73d8f0eecc90>
mock_github_client = <MagicMock name='github_client' id='127375611294928'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='127375611296608'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:31:33.002579Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": "octocat/.github"}, "event": "No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:31:33.003100Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:31:33.003304Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:31:33.003666Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:31:33.003822Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:31:33.002579Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:31:33.003100Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:31:33.003304Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:31:33.003666Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:31:33.003822Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/repo_policy.py:122
  /home/runner/work/stampbot/stampbot/stampbot/repo_policy.py:122: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    owner_type is not "Organization"

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 2 warnings in 2.48s
operator: core/ReplaceComparisonOperator_NotEq_Eq, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -121,7 +121,7 @@
                 if (
                     owner_type == "Organization"
                     and owner_login
-                    and repo_full_name != f"{owner_login}/.github"
+                    and repo_full_name == f"{owner_login}/.github"
                 ):
                     org_repo_full_name = f"{owner_login}/.github"
                     org_content = await run_in_threadpool(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x72a6e6073150>
mock_github_client = <MagicMock name='github_client' id='126061153652416'>

    @pytest.mark.asyncio
    async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
        """Test org .github stampbot.toml is used when repo config is missing."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["repository"]["full_name"] = "acme/widgets"
        payload["repository"]["default_branch"] = "main"
        payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
        payload["pull_request"]["labels"] = [{"name": "org-approve"}]
        payload["label"] = {"name": "org-approve"}
    
        def get_repo_file_side_effect(
            _installation_id,
            repo_full_name,
            _file_path,
            _ref,
            *,
            missing_repository_is_optional=False,
        ):
            if repo_full_name == "acme/widgets":
                assert missing_repository_is_optional is False
                return None
            if repo_full_name == "acme/.github":
                assert missing_repository_is_optional is True
                return 'approval_labels = ["org-approve"]'
            return None
    
        mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:1178: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:42:40.118605Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "acme/widgets", "org_repo": null}, "event": "No stampbot.toml found in acme/widgets, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:42:40.119031Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:42:40.118605Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'acme/widgets', 'org_repo': None}, 'event': 'No stampbot.toml found in acme/widgets, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:42:40.119031Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 444 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceComparisonOperator_NotEq_Lt, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -121,7 +121,7 @@
                 if (
                     owner_type == "Organization"
                     and owner_login
-                    and repo_full_name != f"{owner_login}/.github"
+                    and repo_full_name < f"{owner_login}/.github"
                 ):
                     org_repo_full_name = f"{owner_login}/.github"
                     org_content = await run_in_threadpool(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c9390fc0440>
mock_github_client = <MagicMock name='github_client' id='136973227282112'>

    @pytest.mark.asyncio
    async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
        """Test org .github stampbot.toml is used when repo config is missing."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["repository"]["full_name"] = "acme/widgets"
        payload["repository"]["default_branch"] = "main"
        payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
        payload["pull_request"]["labels"] = [{"name": "org-approve"}]
        payload["label"] = {"name": "org-approve"}
    
        def get_repo_file_side_effect(
            _installation_id,
            repo_full_name,
            _file_path,
            _ref,
            *,
            missing_repository_is_optional=False,
        ):
            if repo_full_name == "acme/widgets":
                assert missing_repository_is_optional is False
                return None
            if repo_full_name == "acme/.github":
                assert missing_repository_is_optional is True
                return 'approval_labels = ["org-approve"]'
            return None
    
        mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:1178: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:27:33.571411Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "acme/widgets", "org_repo": null}, "event": "No stampbot.toml found in acme/widgets, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:27:33.571824Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:27:33.571411Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'acme/widgets', 'org_repo': None}, 'event': 'No stampbot.toml found in acme/widgets, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:27:33.571824Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 444 passed, 3 deselected, 1 warning in 3.31s
operator: core/ReplaceComparisonOperator_NotEq_LtE, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -121,7 +121,7 @@
                 if (
                     owner_type == "Organization"
                     and owner_login
-                    and repo_full_name != f"{owner_login}/.github"
+                    and repo_full_name <= f"{owner_login}/.github"
                 ):
                     org_repo_full_name = f"{owner_login}/.github"
                     org_content = await run_in_threadpool(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x775693de6660>
mock_github_client = <MagicMock name='github_client' id='131213732155072'>

    @pytest.mark.asyncio
    async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
        """Test org .github stampbot.toml is used when repo config is missing."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["repository"]["full_name"] = "acme/widgets"
        payload["repository"]["default_branch"] = "main"
        payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
        payload["pull_request"]["labels"] = [{"name": "org-approve"}]
        payload["label"] = {"name": "org-approve"}
    
        def get_repo_file_side_effect(
            _installation_id,
            repo_full_name,
            _file_path,
            _ref,
            *,
            missing_repository_is_optional=False,
        ):
            if repo_full_name == "acme/widgets":
                assert missing_repository_is_optional is False
                return None
            if repo_full_name == "acme/.github":
                assert missing_repository_is_optional is True
                return 'approval_labels = ["org-approve"]'
            return None
    
        mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:1178: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:54:35.853138Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "acme/widgets", "org_repo": null}, "event": "No stampbot.toml found in acme/widgets, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:54:35.853593Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:54:35.853138Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'acme/widgets', 'org_repo': None}, 'event': 'No stampbot.toml found in acme/widgets, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:54:35.853593Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 444 passed, 3 deselected, 1 warning in 3.31s
operator: core/ReplaceComparisonOperator_NotEq_Gt, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -121,7 +121,7 @@
                 if (
                     owner_type == "Organization"
                     and owner_login
-                    and repo_full_name != f"{owner_login}/.github"
+                    and repo_full_name > f"{owner_login}/.github"
                 ):
                     org_repo_full_name = f"{owner_login}/.github"
                     org_content = await run_in_threadpool(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_NotEq_GtE, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -121,7 +121,7 @@
                 if (
                     owner_type == "Organization"
                     and owner_login
-                    and repo_full_name != f"{owner_login}/.github"
+                    and repo_full_name >= f"{owner_login}/.github"
                 ):
                     org_repo_full_name = f"{owner_login}/.github"
                     org_content = await run_in_threadpool(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceComparisonOperator_NotEq_Is, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -121,7 +121,7 @@
                 if (
                     owner_type == "Organization"
                     and owner_login
-                    and repo_full_name != f"{owner_login}/.github"
+                    and repo_full_name is f"{owner_login}/.github"
                 ):
                     org_repo_full_name = f"{owner_login}/.github"
                     org_content = await run_in_threadpool(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c478fbfb150>
mock_github_client = <MagicMock name='github_client' id='136646793776832'>

    @pytest.mark.asyncio
    async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
        """Test org .github stampbot.toml is used when repo config is missing."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["repository"]["full_name"] = "acme/widgets"
        payload["repository"]["default_branch"] = "main"
        payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
        payload["pull_request"]["labels"] = [{"name": "org-approve"}]
        payload["label"] = {"name": "org-approve"}
    
        def get_repo_file_side_effect(
            _installation_id,
            repo_full_name,
            _file_path,
            _ref,
            *,
            missing_repository_is_optional=False,
        ):
            if repo_full_name == "acme/widgets":
                assert missing_repository_is_optional is False
                return None
            if repo_full_name == "acme/.github":
                assert missing_repository_is_optional is True
                return 'approval_labels = ["org-approve"]'
            return None
    
        mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:1178: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:00:51.265522Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "acme/widgets", "org_repo": null}, "event": "No stampbot.toml found in acme/widgets, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:00:51.265948Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:00:51.265522Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'acme/widgets', 'org_repo': None}, 'event': 'No stampbot.toml found in acme/widgets, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:00:51.265948Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 444 passed, 3 deselected, 1 warning in 3.26s
operator: core/ReplaceComparisonOperator_NotEq_IsNot, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -121,7 +121,7 @@
                 if (
                     owner_type == "Organization"
                     and owner_login
-                    and repo_full_name != f"{owner_login}/.github"
+                    and repo_full_name is not f"{owner_login}/.github"
                 ):
                     org_repo_full_name = f"{owner_login}/.github"
                     org_content = await run_in_threadpool(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 0, definition_name: __init__
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -42,7 +42,7 @@
             cache_seconds: Seconds to keep a valid result. Zero disables caching.
         """
         self._cache: TTLCache[PolicyKey, RepoConfig] | None = (
-            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds > 0 else None
+            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds == 0 else None
         )
 
     async def get(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7e993a1efee0>
mock_github_client = <MagicMock name='github_client' id='139196591852912'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='139196591853920'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:36:09.060686Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:36:09.061102Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:36:09.061313Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:36:09.061683Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:36:09.061886Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:36:09.062032Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:36:09.060686Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:36:09.061102Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:36:09.061313Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:36:09.061683Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:36:09.061886Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:36:09.062032Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.50s
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 0, definition_name: __init__
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -42,7 +42,7 @@
             cache_seconds: Seconds to keep a valid result. Zero disables caching.
         """
         self._cache: TTLCache[PolicyKey, RepoConfig] | None = (
-            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds > 0 else None
+            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds != 0 else None
         )
 
     async def get(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.43s
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 0, definition_name: __init__
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -42,7 +42,7 @@
             cache_seconds: Seconds to keep a valid result. Zero disables caching.
         """
         self._cache: TTLCache[PolicyKey, RepoConfig] | None = (
-            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds > 0 else None
+            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds < 0 else None
         )
 
     async def get(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fb9d93d00c0>
mock_github_client = <MagicMock name='github_client' id='140436212206960'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='140436212207968'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:55:05.475362Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:55:05.475781Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:55:05.475965Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:55:05.476343Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:55:05.476550Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:55:05.476719Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:55:05.475362Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:55:05.475781Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:55:05.475965Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:55:05.476343Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:55:05.476550Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:55:05.476719Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.49s
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 0, definition_name: __init__
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -42,7 +42,7 @@
             cache_seconds: Seconds to keep a valid result. Zero disables caching.
         """
         self._cache: TTLCache[PolicyKey, RepoConfig] | None = (
-            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds > 0 else None
+            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds <= 0 else None
         )
 
     async def get(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x79d6ab05d5c0>
mock_github_client = <MagicMock name='github_client' id='133962919202160'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='133962919203168'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:51:18.203607Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:51:18.204012Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:51:18.204213Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:51:18.204586Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:51:18.204806Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:51:18.204952Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:51:18.203607Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:51:18.204012Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:51:18.204213Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:51:18.204586Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:51:18.204806Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:51:18.204952Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.49s
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 0, definition_name: __init__
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -42,7 +42,7 @@
             cache_seconds: Seconds to keep a valid result. Zero disables caching.
         """
         self._cache: TTLCache[PolicyKey, RepoConfig] | None = (
-            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds > 0 else None
+            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds >= 0 else None
         )
 
     async def get(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 0, definition_name: get
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -76,7 +76,7 @@
             installation_id, repo_full_name, default_branch, owner_login, owner_type
         )
 
-        if self._cache is not None and repo_config.config_error is None:
+        if self._cache is not None and repo_config.config_error is not None:
             self._cache[key] = repo_config
 
         return repo_config
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7077c10ed5c0>
mock_github_client = <MagicMock name='github_client' id='123659661938032'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='123659661939040'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:29:34.399620Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:29:34.400040Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:29:34.400254Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:29:34.400633Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:29:34.400850Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:29:34.401001Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:29:34.399620Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:29:34.400040Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:29:34.400254Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:29:34.400633Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:29:34.400850Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:29:34.401001Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 0, definition_name: get
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -66,7 +66,7 @@
             RepoConfig instance. ``config_error`` is set when automation must stop.
         """
         key: PolicyKey = (installation_id, repo_full_name, default_branch)
-        if self._cache is not None:
+        if self._cache is None:
             cached = self._cache.get(key)
             if cached is not None:
                 repo_config_loads_total.labels(status="cached").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7dcc0bc1be70>
mock_github_client = <MagicMock name='github_client' id='138315414959312'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='138315414960992'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:17:13.392495Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:17:13.392922Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:17:13.393112Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:17:13.393509Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:17:13.393711Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:17:13.393861Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:17:13.392495Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:17:13.392922Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:17:13.393112Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:17:13.393509Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:17:13.393711Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:17:13.393861Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.46s
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 1, definition_name: get
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -68,7 +68,7 @@
         key: PolicyKey = (installation_id, repo_full_name, default_branch)
         if self._cache is not None:
             cached = self._cache.get(key)
-            if cached is not None:
+            if cached is None:
                 repo_config_loads_total.labels(status="cached").inc()
                 return cached
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7bc81b3290f0>
mock_github_client = <MagicMock name='github_client' id='136099401078096'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7bc81b3290f0>
payload = {'action': 'opened', 'number': 42, 'pull_request': {'url': 'https://api.github.com/repos/octocat/hello-world/pulls/42'...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
>           if repo_config.config_error:
               ^^^^^^^^^^^^^^^^^^^^^^^^
E           AttributeError: 'NoneType' object has no attribute 'config_error'

stampbot/webhook_handler.py:232: AttributeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:33:50.034022Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:33:50.034022Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AttributeError: 'NoneType' object has no attribute 'config_error'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 2, definition_name: get
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -76,7 +76,7 @@
             installation_id, repo_full_name, default_branch, owner_login, owner_type
         )
 
-        if self._cache is not None and repo_config.config_error is None:
+        if self._cache is None and repo_config.config_error is None:
             self._cache[key] = repo_config
 
         return repo_config
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d75a6e795c0>
mock_github_client = <MagicMock name='github_client' id='137944284810608'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='137944284811616'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:29:21.265960Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:29:21.266385Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:29:21.266573Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:29:21.266943Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:29:21.267145Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:29:21.267320Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:29:21.265960Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:29:21.266385Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:29:21.266573Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:29:21.266943Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:29:21.267145Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:29:21.267320Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.48s
operator: core/AddNot, occurrence: 0, definition_name: __init__
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -42,7 +42,7 @@
             cache_seconds: Seconds to keep a valid result. Zero disables caching.
         """
         self._cache: TTLCache[PolicyKey, RepoConfig] | None = (
-            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds > 0 else None
+            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if not cache_seconds > 0 else None
         )
 
     async def get(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7086a3e5fb60>
mock_github_client = <MagicMock name='github_client' id='123723666836848'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='123723666837856'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:27:27.673942Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:27:27.674370Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:27:27.674557Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:27:27.674921Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:27:27.675118Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:27:27.675287Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:27:27.673942Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:27:27.674370Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:27:27.674557Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:27:27.674921Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:27:27.675118Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:27:27.675287Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.47s
operator: core/AddNot, occurrence: 1, definition_name: get
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -66,7 +66,7 @@
             RepoConfig instance. ``config_error`` is set when automation must stop.
         """
         key: PolicyKey = (installation_id, repo_full_name, default_branch)
-        if self._cache is not None:
+        if not self._cache is not None:
             cached = self._cache.get(key)
             if cached is not None:
                 repo_config_loads_total.labels(status="cached").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x72a7f4813b60>
mock_github_client = <MagicMock name='github_client' id='126065710150864'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='126065710152544'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:38:35.412548Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:38:35.412973Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:38:35.413187Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:38:35.413574Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:38:35.413788Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:38:35.413943Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:38:35.412548Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:38:35.412973Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:38:35.413187Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:38:35.413574Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:38:35.413788Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:38:35.413943Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.48s
operator: core/AddNot, occurrence: 2, definition_name: get
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -68,7 +68,7 @@
         key: PolicyKey = (installation_id, repo_full_name, default_branch)
         if self._cache is not None:
             cached = self._cache.get(key)
-            if cached is not None:
+            if not cached is not None:
                 repo_config_loads_total.labels(status="cached").inc()
                 return cached
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f70d01190f0>
mock_github_client = <MagicMock name='github_client' id='140122525832528'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7f70d01190f0>
payload = {'action': 'opened', 'number': 42, 'pull_request': {'url': 'https://api.github.com/repos/octocat/hello-world/pulls/42'...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
>           if repo_config.config_error:
               ^^^^^^^^^^^^^^^^^^^^^^^^
E           AttributeError: 'NoneType' object has no attribute 'config_error'

stampbot/webhook_handler.py:232: AttributeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:12:39.803726Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:12:39.803726Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AttributeError: 'NoneType' object has no attribute 'config_error'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.38s
operator: core/AddNot, occurrence: 3, definition_name: get
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -76,7 +76,7 @@
             installation_id, repo_full_name, default_branch, owner_login, owner_type
         )
 
-        if self._cache is not None and repo_config.config_error is None:
+        if not self._cache is not None and repo_config.config_error is None:
             self._cache[key] = repo_config
 
         return repo_config
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x78abe72ebb60>
mock_github_client = <MagicMock name='github_client' id='132679733972336'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='132679733973344'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:29.710379Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:18:29.710829Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:29.711012Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:29.711416Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:18:29.711626Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:29.711769Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:29.710379Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:18:29.710829Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:29.711012Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:29.711416Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:18:29.711626Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:29.711769Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/hello-world', 'stampbot.toml', 'main')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.51s
operator: core/AddNot, occurrence: 4, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -114,7 +114,7 @@
                     default_branch,
                 )
 
-                if content:
+                if not content:
                     return self._parse(span, content, source_repo=repo_full_name)
 
                 org_repo_full_name = None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f0f24581a90>
mock_github_client = <MagicMock name='github_client' id='139703038456704'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:198: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:22:51.077050Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:22:51.077525Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:22:51.077050Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:22:51.077525Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.44s
operator: core/AddNot, occurrence: 5, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -118,7 +118,7 @@
                     return self._parse(span, content, source_repo=repo_full_name)
 
                 org_repo_full_name = None
-                if (
+                if not (
                     owner_type == "Organization"
                     and owner_login
                     and repo_full_name != f"{owner_login}/.github"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7561a30d80c0>
mock_github_client = <MagicMock name='github_client' id='129062231012560'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='129062231014240'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:40:13.599901Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": "octocat/.github"}, "event": "No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:40:13.600421Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:40:13.600605Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:40:13.600994Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:40:13.601154Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:40:13.599901Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:40:13.600421Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:40:13.600605Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:40:13.600994Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:40:13.601154Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.49s
operator: core/AddNot, occurrence: 6, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -132,7 +132,7 @@
                         None,
                         missing_repository_is_optional=True,
                     )
-                    if org_content:
+                    if not org_content:
                         return self._parse(span, org_content, source_repo=org_repo_full_name)
 
                 repo_config = RepoConfig.default_or_config_error()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d8f1d70a6d0>
mock_github_client = <MagicMock name='github_client' id='138053625908928'>

    @pytest.mark.asyncio
    async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
        """Test org .github stampbot.toml is used when repo config is missing."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["repository"]["full_name"] = "acme/widgets"
        payload["repository"]["default_branch"] = "main"
        payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
        payload["pull_request"]["labels"] = [{"name": "org-approve"}]
        payload["label"] = {"name": "org-approve"}
    
        def get_repo_file_side_effect(
            _installation_id,
            repo_full_name,
            _file_path,
            _ref,
            *,
            missing_repository_is_optional=False,
        ):
            if repo_full_name == "acme/widgets":
                assert missing_repository_is_optional is False
                return None
            if repo_full_name == "acme/.github":
                assert missing_repository_is_optional is True
                return 'approval_labels = ["org-approve"]'
            return None
    
        mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:1178: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:19:21.335283Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "acme/widgets", "org_repo": "acme/.github"}, "event": "No stampbot.toml found in acme/widgets or acme/.github, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:19:21.335811Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:19:21.335283Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'acme/widgets', 'org_repo': 'acme/.github'}, 'event': 'No stampbot.toml found in acme/widgets or acme/.github, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:19:21.335811Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 444 passed, 3 deselected, 1 warning in 3.34s
operator: core/AddNot, occurrence: 7, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -136,7 +136,7 @@
                         return self._parse(span, org_content, source_repo=org_repo_full_name)
 
                 repo_config = RepoConfig.default_or_config_error()
-                if repo_config.config_error:
+                if not repo_config.config_error:
                     error = ValueError(repo_config.config_error)
                     repo_config_loads_total.labels(status="error").inc()
                     logger.warning(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/AddNot, occurrence: 8, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -160,7 +160,7 @@
                 logger.info(
                     "No stampbot.toml found in %s%s, using defaults",
                     repo_full_name,
-                    f" or {org_repo_full_name}" if org_repo_full_name else "",
+                    f" or {org_repo_full_name}" if not org_repo_full_name else "",
                     extra={"repo": repo_full_name, "org_repo": org_repo_full_name},
                 )
                 add_span_attributes(span, {"config.result": "default"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceTrueWithFalse, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -130,7 +130,7 @@
                         org_repo_full_name,
                         POLICY_FILE,
                         None,
-                        missing_repository_is_optional=True,
+                        missing_repository_is_optional=False,
                     )
                     if org_content:
                         return self._parse(span, org_content, source_repo=org_repo_full_name)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............F
=================================== FAILURES ===================================
_____________________ test_org_github_repo_config_fallback _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73e48850f150>
mock_github_client = <MagicMock name='github_client' id='127425374949056'>

    @pytest.mark.asyncio
    async def test_org_github_repo_config_fallback(webhook_handler, mock_github_client):
        """Test org .github stampbot.toml is used when repo config is missing."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["repository"]["full_name"] = "acme/widgets"
        payload["repository"]["default_branch"] = "main"
        payload["repository"]["owner"] = {"login": "acme", "type": "Organization"}
        payload["pull_request"]["labels"] = [{"name": "org-approve"}]
        payload["label"] = {"name": "org-approve"}
    
        def get_repo_file_side_effect(
            _installation_id,
            repo_full_name,
            _file_path,
            _ref,
            *,
            missing_repository_is_optional=False,
        ):
            if repo_full_name == "acme/widgets":
                assert missing_repository_is_optional is False
                return None
            if repo_full_name == "acme/.github":
                assert missing_repository_is_optional is True
                return 'approval_labels = ["org-approve"]'
            return None
    
        mock_github_client.get_repo_file.side_effect = get_repo_file_side_effect
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:1178: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:22:47.049778Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "acme/widgets", "error": "assert False is True"}, "event": "Error loading config from acme/widgets: assert False is True, disabling automation", "logger": "stampbot.repo_policy", "level": "warning", "timestamp": "2026-11-04T03:22:47.050429Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "warning"}
{"extra": {"repo": "acme/widgets", "error": "Unable to load Stampbot configuration; automation is disabled"}, "event": "Invalid Stampbot configuration for acme/widgets: Unable to load Stampbot configuration; automation is disabled", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:22:47.050539Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:22:47.049778Z'}
WARNING  stampbot.repo_policy:repo_policy.py:172 {'extra': {'repo': 'acme/widgets', 'error': 'assert False is True'}, 'event': 'Error loading config from acme/widgets: assert False is True, disabling automation', 'logger': 'stampbot.repo_policy', 'level': 'warning', 'timestamp': '2026-11-04T03:22:47.050429Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:233 {'extra': {'repo': 'acme/widgets', 'error': 'Unable to load Stampbot configuration; automation is disabled'}, 'event': 'Invalid Stampbot configuration for acme/widgets: Unable to load Stampbot configuration; automation is disabled', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:22:47.050539Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_org_github_repo_config_fallback - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 444 passed, 3 deselected, 1 warning in 3.30s
operator: core/ReplaceAndWithOr, occurrence: 0, definition_name: get
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -76,7 +76,7 @@
             installation_id, repo_full_name, default_branch, owner_login, owner_type
         )
 
-        if self._cache is not None and repo_config.config_error is None:
+        if self._cache is not None or repo_config.config_error is None:
             self._cache[key] = repo_config
 
         return repo_config
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
............................F
=================================== FAILURES ===================================
_______________________ test_repo_config_cache_disabled ________________________

mock_github_client = <MagicMock name='github_client' id='127175626771152'>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x73aa60be4130>

    @pytest.mark.asyncio
    async def test_repo_config_cache_disabled(mock_github_client, monkeypatch):
        """Test a zero cache lifetime reads policy on every event."""
        monkeypatch.setattr("stampbot.webhook_handler.repo_config_cache_seconds", lambda: 0)
        from stampbot.webhook_handler import WebhookHandler
    
        handler = WebhookHandler()
        payload = load_fixture("pr_labeled_autoapprove")
    
>       await handler.handle_event("pull_request", payload)

tests/test_webhook_handler.py:399: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:224: in _handle_pull_request
    repo_config = await self._policy.get(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.repo_policy.RepoPolicyResolver object at 0x73aa61eca1c0>
installation_id = 12345, repo_full_name = 'octocat/hello-world'
default_branch = 'main', owner_login = 'octocat', owner_type = 'User'

    async def get(
        self,
        installation_id: int,
        repo_full_name: str,
        default_branch: str,
        owner_login: str | None,
        owner_type: str | None,
    ) -> RepoConfig:
        """Return the effective policy for a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            default_branch: Repository default branch
            owner_login: Repository owner login
            owner_type: Repository owner type (Organization/User)
    
        Returns:
            RepoConfig instance. ``config_error`` is set when automation must stop.
        """
        key: PolicyKey = (installation_id, repo_full_name, default_branch)
        if self._cache is not None:
            cached = self._cache.get(key)
            if cached is not None:
                repo_config_loads_total.labels(status="cached").inc()
                return cached
    
        repo_config = await self._load(
            installation_id, repo_full_name, default_branch, owner_login, owner_type
        )
    
        if self._cache is not None or repo_config.config_error is None:
>           self._cache[key] = repo_config
            ^^^^^^^^^^^^^^^^
E           TypeError: 'NoneType' object does not support item assignment

stampbot/repo_policy.py:80: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:15:26.337668Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:15:26.338072Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:15:26.337668Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:15:26.338072Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_cache_disabled - TypeError: 'NoneType' object does not support item assignment
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 388 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceAndWithOr, occurrence: 1, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -120,7 +120,7 @@
                 org_repo_full_name = None
                 if (
                     owner_type == "Organization"
-                    and owner_login
+                    or owner_login
                     and repo_full_name != f"{owner_login}/.github"
                 ):
                     org_repo_full_name = f"{owner_login}/.github"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73f9a364ff50>
mock_github_client = <MagicMock name='github_client' id='127516047209680'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='127516047211360'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:22:00.315306Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": "octocat/.github"}, "event": "No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:22:00.315805Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:22:00.315982Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:22:00.316363Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:22:00.316527Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:22:00.315306Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:22:00.315805Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:22:00.315982Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:22:00.316363Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:22:00.316527Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceAndWithOr, occurrence: 2, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -121,7 +121,7 @@
                 if (
                     owner_type == "Organization"
                     and owner_login
-                    and repo_full_name != f"{owner_login}/.github"
+                    or repo_full_name != f"{owner_login}/.github"
                 ):
                     org_repo_full_name = f"{owner_login}/.github"
                     org_content = await run_in_threadpool(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...........................F
=================================== FAILURES ===================================
__________________ test_repo_config_is_cached_between_events ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7145aab63e70>
mock_github_client = <MagicMock name='github_client' id='124544053359824'>

    @pytest.mark.asyncio
    async def test_repo_config_is_cached_between_events(webhook_handler, mock_github_client):
        """Test a second event for the same repository reuses the parsed policy."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        first = await webhook_handler.handle_event("pull_request", payload)
        second = await webhook_handler.handle_event("pull_request", payload)
    
        assert first["status"] == "success"
        assert second["status"] == "success"
>       mock_github_client.get_repo_file.assert_called_once()

tests/test_webhook_handler.py:386: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='124544053361504'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
E            call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:21.031072Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": "octocat/.github"}, "event": "No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:42:21.031599Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:21.031785Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:21.032135Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:21.032318Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:21.031072Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': 'octocat/.github'}, 'event': 'No stampbot.toml found in octocat/hello-world or octocat/.github, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:42:21.031599Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:21.031785Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:21.032135Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:21.032318Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_is_cached_between_events - AssertionError: Expected 'get_repo_file' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 'stampbot.toml', 'main'),
 call(12345, 'octocat/.github', 'stampbot.toml', None, missing_repository_is_optional=True)].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 387 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceOrWithAnd, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -103,7 +103,7 @@
         """
         with create_span(
             "webhook.get_repo_config",
-            {"github.repo": repo_full_name, "github.ref": default_branch or "default"},
+            {"github.repo": repo_full_name, "github.ref": default_branch and "default"},
         ) as span:
             try:
                 content = await run_in_threadpool(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ExceptionReplacer, occurrence: 0, definition_name: _load
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -167,7 +167,7 @@
                 set_span_ok(span)
                 return repo_config
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 repo_config_loads_total.labels(status="error").inc()
                 logger.warning(
                     "Error loading config from %s: %s, disabling automation",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
..............F
=================================== FAILURES ===================================
___________________ test_target_repo_not_found_fails_closed ____________________

self = <stampbot.repo_policy.RepoPolicyResolver object at 0x73b9b3881810>
installation_id = 12345, repo_full_name = 'octocat/hello-world'
default_branch = 'main', owner_login = 'octocat', owner_type = 'User'

    async def _load(
        self,
        installation_id: int,
        repo_full_name: str,
        default_branch: str,
        owner_login: str | None,
        owner_type: str | None,
    ) -> RepoConfig:
        """Read policy from GitHub without consulting the cache.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            default_branch: Repository default branch
            owner_login: Repository owner login
            owner_type: Repository owner type (Organization/User)
    
        Returns:
            RepoConfig instance (default if file not found)
        """
        with create_span(
            "webhook.get_repo_config",
            {"github.repo": repo_full_name, "github.ref": default_branch or "default"},
        ) as span:
            try:
>               content = await run_in_threadpool(
                    github_client.get_repo_file,
                    installation_id,
                    repo_full_name,
                    POLICY_FILE,
                    default_branch,
                )

stampbot/repo_policy.py:109: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:34: in run_in_threadpool
    return await anyio.to_thread.run_sync(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:65: in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2779: in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:1124: in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_repo_file' id='127241396117456'>
args = (12345, 'octocat/hello-world', 'stampbot.toml', 'main'), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73b9b22b3150>
mock_github_client = <MagicMock name='github_client' id='127241396117120'>

    @pytest.mark.asyncio
    async def test_target_repo_not_found_fails_closed(webhook_handler, mock_github_client):
        """Test a target-repository 404 never advances to service defaults."""
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.get_repo_file.side_effect = GithubException(
            404,
            {"message": "Not Found"},
            None,
        )
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1228: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:224: in _handle_pull_request
    repo_config = await self._policy.get(
stampbot/repo_policy.py:75: in get
    repo_config = await self._load(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.repo_policy.RepoPolicyResolver object at 0x73b9b3881810>
installation_id = 12345, repo_full_name = 'octocat/hello-world'
default_branch = 'main', owner_login = 'octocat', owner_type = 'User'

    async def _load(
        self,
        installation_id: int,
        repo_full_name: str,
        default_branch: str,
        owner_login: str | None,
        owner_type: str | None,
    ) -> RepoConfig:
        """Read policy from GitHub without consulting the cache.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            default_branch: Repository default branch
            owner_login: Repository owner login
            owner_type: Repository owner type (Organization/User)
    
        Returns:
            RepoConfig instance (default if file not found)
        """
        with create_span(
            "webhook.get_repo_config",
            {"github.repo": repo_full_name, "github.ref": default_branch or "default"},
        ) as span:
            try:
                content = await run_in_threadpool(
                    github_client.get_repo_file,
                    installation_id,
                    repo_full_name,
                    POLICY_FILE,
                    default_branch,
                )
    
                if content:
                    return self._parse(span, content, source_repo=repo_full_name)
    
                org_repo_full_name = None
                if (
                    owner_type == "Organization"
                    and owner_login
                    and repo_full_name != f"{owner_login}/.github"
                ):
                    org_repo_full_name = f"{owner_login}/.github"
                    org_content = await run_in_threadpool(
                        github_client.get_repo_file,
                        installation_id,
                        org_repo_full_name,
                        POLICY_FILE,
                        None,
                        missing_repository_is_optional=True,
                    )
                    if org_content:
                        return self._parse(span, org_content, source_repo=org_repo_full_name)
    
                repo_config = RepoConfig.default_or_config_error()
                if repo_config.config_error:
                    error = ValueError(repo_config.config_error)
                    repo_config_loads_total.labels(status="error").inc()
                    logger.warning(
                        "Invalid service default configuration for %s: %s",
                        repo_full_name,
                        repo_config.config_error,
                        extra={"repo": repo_full_name, "error": repo_config.config_error},
                    )
                    add_span_attributes(
                        span,
                        {
                            "config.result": "error",
                            "config.error": repo_config.config_error,
                            "config.source_repo": "service_defaults",
                        },
                    )
                    set_span_error(span, error)
                    return repo_config
    
                repo_config_loads_total.labels(status="default").inc()
                logger.info(
                    "No stampbot.toml found in %s%s, using defaults",
                    repo_full_name,
                    f" or {org_repo_full_name}" if org_repo_full_name else "",
                    extra={"repo": repo_full_name, "org_repo": org_repo_full_name},
                )
                add_span_attributes(span, {"config.result": "default"})
                set_span_ok(span)
                return repo_config
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/repo_policy.py:170: NameError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:26:48.095903Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:26:48.095903Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_target_repo_not_found_fails_closed - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 446 passed, 3 deselected, 1 warning in 3.37s
operator: core/ExceptionReplacer, occurrence: 1, definition_name: _parse
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -195,7 +195,7 @@
         """
         try:
             repo_config = RepoConfig.from_toml(toml_content)
-        except ValueError as e:
+        except CosmicRayTestingException as e:
             repo_config_loads_total.labels(status="error").inc()
             add_span_attributes(
                 span,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
........................F
=================================== FAILURES ===================================
_____ test_invalid_repo_and_service_configs_use_builtin_fail_closed_config _____

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x752cacaa52b0>
mock_github_client = <MagicMock name='github_client' id='128834734677296'>

    @pytest.mark.asyncio
    async def test_invalid_repo_and_service_configs_use_builtin_fail_closed_config(
        webhook_handler, mock_github_client
    ):
        """Test repo errors do not re-read invalid service defaults for fallback."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        mock_github_client.get_repo_file.return_value = 'chatops_required_permission = "invalid"'
        service_defaults = MagicMock(spec=["approve_commands"])
        service_defaults.approve_commands = "approve"
    
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = service_defaults
            mock_settings.defaults = service_defaults
    
            result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result == {"status": "error", "message": "Invalid repository configuration"}
        review_body = mock_github_client.create_pr_review_comment.call_args.args[3]
>       assert "Invalid chatops_required_permission" in review_body
E       AssertionError: assert 'Invalid chatops_required_permission' in 'Stampbot configuration error:\n\nUnable to load Stampbot configuration; automation is disabled\n\nPlease fix the configuration to re-enable automation.'

tests/test_webhook_handler.py:1397: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:13:22.061464Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "error": "name 'CosmicRayTestingException' is not defined"}, "event": "Error loading config from octocat/hello-world: name 'CosmicRayTestingException' is not defined, disabling automation", "logger": "stampbot.repo_policy", "level": "warning", "timestamp": "2026-11-04T03:13:22.061898Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "warning"}
{"extra": {"repo": "octocat/hello-world", "error": "Unable to load Stampbot configuration; automation is disabled"}, "event": "Invalid Stampbot configuration for octocat/hello-world: Unable to load Stampbot configuration; automation is disabled", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:13:22.061996Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:13:22.061464Z'}
WARNING  stampbot.repo_policy:repo_policy.py:172 {'extra': {'repo': 'octocat/hello-world', 'error': "name 'CosmicRayTestingException' is not defined"}, 'event': "Error loading config from octocat/hello-world: name 'CosmicRayTestingException' is not defined, disabling automation", 'logger': 'stampbot.repo_policy', 'level': 'warning', 'timestamp': '2026-11-04T03:13:22.061898Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:233 {'extra': {'repo': 'octocat/hello-world', 'error': 'Unable to load Stampbot configuration; automation is disabled'}, 'event': 'Invalid Stampbot configuration for octocat/hello-world: Unable to load Stampbot configuration; automation is disabled', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:13:22.061996Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_invalid_repo_and_service_configs_use_builtin_fail_closed_config - AssertionError: assert 'Invalid chatops_required_permission' in 'Stampbot configuration error:\n\nUnable to load Stampbot configuration; automation is disabled\n\nPlease fix the configuration to re-enable automation.'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 456 passed, 3 deselected, 1 warning in 3.31s
operator: core/NumberReplacer, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -17,7 +17,7 @@
 
 POLICY_FILE = "stampbot.toml"
 # Upper bound on repositories whose policy one replica keeps in memory.
-POLICY_CACHE_SIZE = 1024
+POLICY_CACHE_SIZE = 1025
 
 PolicyKey = tuple[int, str, str]
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.42s
operator: core/NumberReplacer, occurrence: 1, definition_name: None
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -17,7 +17,7 @@
 
 POLICY_FILE = "stampbot.toml"
 # Upper bound on repositories whose policy one replica keeps in memory.
-POLICY_CACHE_SIZE = 1024
+POLICY_CACHE_SIZE = 1023
 
 PolicyKey = tuple[int, str, str]
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 2, definition_name: __init__
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -42,7 +42,7 @@
             cache_seconds: Seconds to keep a valid result. Zero disables caching.
         """
         self._cache: TTLCache[PolicyKey, RepoConfig] | None = (
-            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds > 0 else None
+            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds > 1 else None
         )
 
     async def get(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/NumberReplacer, occurrence: 3, definition_name: __init__
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -42,7 +42,7 @@
             cache_seconds: Seconds to keep a valid result. Zero disables caching.
         """
         self._cache: TTLCache[PolicyKey, RepoConfig] | None = (
-            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds > 0 else None
+            TTLCache(maxsize=POLICY_CACHE_SIZE, ttl=cache_seconds) if cache_seconds > -1 else None
         )
 
     async def get(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/RemoveDecorator, occurrence: 0, definition_name: RepoPolicyResolver
--- mutation diff ---
--- a/stampbot/repo_policy.py
+++ b/stampbot/repo_policy.py
@@ -180,8 +180,6 @@
                 return RepoConfig.fail_closed(
                     "Unable to load Stampbot configuration; automation is disabled"
                 )
-
-    @staticmethod
     def _parse(span: Span | None, toml_content: str, source_repo: str) -> RepoConfig:
         """Parse policy content with metrics and tracing.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fdbb5af07d0>
mock_github_client = <MagicMock name='github_client' id='140581644465024'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:198: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:44:05.917074Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "error": "RepoPolicyResolver._parse() got multiple values for argument 'source_repo'"}, "event": "Error loading config from octocat/hello-world: RepoPolicyResolver._parse() got multiple values for argument 'source_repo', disabling automation", "logger": "stampbot.repo_policy", "level": "warning", "timestamp": "2026-11-04T04:44:05.917466Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "warning"}
{"extra": {"repo": "octocat/hello-world", "error": "Unable to load Stampbot configuration; automation is disabled"}, "event": "Invalid Stampbot configuration for octocat/hello-world: Unable to load Stampbot configuration; automation is disabled", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:44:05.917562Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:44:05.917074Z'}
WARNING  stampbot.repo_policy:repo_policy.py:172 {'extra': {'repo': 'octocat/hello-world', 'error': "RepoPolicyResolver._parse() got multiple values for argument 'source_repo'"}, 'event': "Error loading config from octocat/hello-world: RepoPolicyResolver._parse() got multiple values for argument 'source_repo', disabling automation", 'logger': 'stampbot.repo_policy', 'level': 'warning', 'timestamp': '2026-11-04T04:44:05.917466Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:233 {'extra': {'repo': 'octocat/hello-world', 'error': 'Unable to load Stampbot configuration; automation is disabled'}, 'event': 'Invalid Stampbot configuration for octocat/hello-world: Unable to load Stampbot configuration; automation is disabled', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:44:05.917562Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -86,6 +86,6 @@
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
-                    time.time() - start_time
+                    time.time() + start_time
                 )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -86,6 +86,6 @@
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
-                    time.time() - start_time
+                    time.time() * start_time
                 )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -86,6 +86,6 @@
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
-                    time.time() - start_time
+                    time.time() / start_time
                 )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -86,6 +86,6 @@
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
-                    time.time() - start_time
+                    time.time() // start_time
                 )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -86,6 +86,6 @@
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
-                    time.time() - start_time
+                    time.time() % start_time
                 )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -86,6 +86,6 @@
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
-                    time.time() - start_time
+                    time.time() ** start_time
                 )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..................................................F
=================================== FAILURES ===================================
_ TestInstrumentedInstallationAuth.test_token_exchanges_once_and_refreshes_when_expired _

self = <tests.test_installation_auth.TestInstrumentedInstallationAuth object at 0x71ba82d84b90>
installation_auth = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x71ba83c02cf0>
span = <MagicMock name='create_span' id='125045888265120'>

    def test_token_exchanges_once_and_refreshes_when_expired(self, installation_auth, span):
        """Test PyGithub's ``token`` routes first exchange and refresh through the override."""
        successes = _counter("success")
        durations = _duration_count()
        with patch.object(
            Auth.AppInstallationAuth,
            EXCHANGE,
            side_effect=[_authorization(expired=True), _authorization(expired=False)],
        ) as exchange:
>           assert installation_auth.token == TEST_TOKEN  # first exchange
                   ^^^^^^^^^^^^^^^^^^^^^^^

tests/test_installation_auth.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/installation_auth.py:57: in token
    return super().token
           ^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/github/Auth.py:378: in token
    self.__installation_authorization = self._get_installation_authorization()
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x71ba83c02cf0>

    def _get_installation_authorization(self) -> InstallationAuthorization:
        """Exchange the App JWT for an installation token, with a span and metrics.
    
        Returns:
            The new installation token and its expiry.
    
        Raises:
            github.GithubException: If GitHub rejects the exchange.
        """
        start_time = time.time()
        with create_span(
            "github.get_installation_token",
            {
                "github.installation_id": self.installation_id,
                "github.token_refresh": self._exchanged,
            },
        ) as span:
            try:
                authorization = super()._get_installation_authorization()
            except Exception as e:
                github_api_requests_total.labels(operation="get_token", status="failure").inc()
                set_span_error(span, e)
                raise
            else:
                github_api_requests_total.labels(operation="get_token", status="success").inc()
                set_span_ok(span)
                self._exchanged = True
                return authorization
            finally:
                github_api_request_duration_seconds.labels(operation="get_token").observe(
>                   time.time() ** start_time
                )
E               OverflowError: (34, 'Numerical result out of range')

stampbot/installation_auth.py:89: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_installation_auth.py::TestInstrumentedInstallationAuth::test_token_exchanges_once_and_refreshes_when_expired - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 194 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -86,6 +86,6 @@
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
-                    time.time() - start_time
+                    time.time() >> start_time
                 )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..................................................F
=================================== FAILURES ===================================
_ TestInstrumentedInstallationAuth.test_token_exchanges_once_and_refreshes_when_expired _

self = <tests.test_installation_auth.TestInstrumentedInstallationAuth object at 0x75f4f0d80b90>
installation_auth = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x75f4f1dfecf0>
span = <MagicMock name='create_span' id='129694890453920'>

    def test_token_exchanges_once_and_refreshes_when_expired(self, installation_auth, span):
        """Test PyGithub's ``token`` routes first exchange and refresh through the override."""
        successes = _counter("success")
        durations = _duration_count()
        with patch.object(
            Auth.AppInstallationAuth,
            EXCHANGE,
            side_effect=[_authorization(expired=True), _authorization(expired=False)],
        ) as exchange:
>           assert installation_auth.token == TEST_TOKEN  # first exchange
                   ^^^^^^^^^^^^^^^^^^^^^^^

tests/test_installation_auth.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/installation_auth.py:57: in token
    return super().token
           ^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/github/Auth.py:378: in token
    self.__installation_authorization = self._get_installation_authorization()
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x75f4f1dfecf0>

    def _get_installation_authorization(self) -> InstallationAuthorization:
        """Exchange the App JWT for an installation token, with a span and metrics.
    
        Returns:
            The new installation token and its expiry.
    
        Raises:
            github.GithubException: If GitHub rejects the exchange.
        """
        start_time = time.time()
        with create_span(
            "github.get_installation_token",
            {
                "github.installation_id": self.installation_id,
                "github.token_refresh": self._exchanged,
            },
        ) as span:
            try:
                authorization = super()._get_installation_authorization()
            except Exception as e:
                github_api_requests_total.labels(operation="get_token", status="failure").inc()
                set_span_error(span, e)
                raise
            else:
                github_api_requests_total.labels(operation="get_token", status="success").inc()
                set_span_ok(span)
                self._exchanged = True
                return authorization
            finally:
                github_api_request_duration_seconds.labels(operation="get_token").observe(
>                   time.time() >> start_time
                )
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/installation_auth.py:89: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_installation_auth.py::TestInstrumentedInstallationAuth::test_token_exchanges_once_and_refreshes_when_expired - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 194 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -86,6 +86,6 @@
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
-                    time.time() - start_time
+                    time.time() << start_time
                 )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..................................................F
=================================== FAILURES ===================================
_ TestInstrumentedInstallationAuth.test_token_exchanges_once_and_refreshes_when_expired _

self = <tests.test_installation_auth.TestInstrumentedInstallationAuth object at 0x7759ecd88b90>
installation_auth = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x7759edc22cf0>
span = <MagicMock name='create_span' id='131228124720032'>

    def test_token_exchanges_once_and_refreshes_when_expired(self, installation_auth, span):
        """Test PyGithub's ``token`` routes first exchange and refresh through the override."""
        successes = _counter("success")
        durations = _duration_count()
        with patch.object(
            Auth.AppInstallationAuth,
            EXCHANGE,
            side_effect=[_authorization(expired=True), _authorization(expired=False)],
        ) as exchange:
>           assert installation_auth.token == TEST_TOKEN  # first exchange
                   ^^^^^^^^^^^^^^^^^^^^^^^

tests/test_installation_auth.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/installation_auth.py:57: in token
    return super().token
           ^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/github/Auth.py:378: in token
    self.__installation_authorization = self._get_installation_authorization()
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x7759edc22cf0>

    def _get_installation_authorization(self) -> InstallationAuthorization:
        """Exchange the App JWT for an installation token, with a span and metrics.
    
        Returns:
            The new installation token and its expiry.
    
        Raises:
            github.GithubException: If GitHub rejects the exchange.
        """
        start_time = time.time()
        with create_span(
            "github.get_installation_token",
            {
                "github.installation_id": self.installation_id,
                "github.token_refresh": self._exchanged,
            },
        ) as span:
            try:
                authorization = super()._get_installation_authorization()
            except Exception as e:
                github_api_requests_total.labels(operation="get_token", status="failure").inc()
                set_span_error(span, e)
                raise
            else:
                github_api_requests_total.labels(operation="get_token", status="success").inc()
                set_span_ok(span)
                self._exchanged = True
                return authorization
            finally:
                github_api_request_duration_seconds.labels(operation="get_token").observe(
>                   time.time() << start_time
                )
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/installation_auth.py:89: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_installation_auth.py::TestInstrumentedInstallationAuth::test_token_exchanges_once_and_refreshes_when_expired - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 194 passed, 3 deselected, 1 warning in 1.54s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -86,6 +86,6 @@
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
-                    time.time() - start_time
+                    time.time() | start_time
                 )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..................................................F
=================================== FAILURES ===================================
_ TestInstrumentedInstallationAuth.test_token_exchanges_once_and_refreshes_when_expired _

self = <tests.test_installation_auth.TestInstrumentedInstallationAuth object at 0x7e454bb80b90>
installation_auth = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x7e454ca0acf0>
span = <MagicMock name='create_span' id='138836103441312'>

    def test_token_exchanges_once_and_refreshes_when_expired(self, installation_auth, span):
        """Test PyGithub's ``token`` routes first exchange and refresh through the override."""
        successes = _counter("success")
        durations = _duration_count()
        with patch.object(
            Auth.AppInstallationAuth,
            EXCHANGE,
            side_effect=[_authorization(expired=True), _authorization(expired=False)],
        ) as exchange:
>           assert installation_auth.token == TEST_TOKEN  # first exchange
                   ^^^^^^^^^^^^^^^^^^^^^^^

tests/test_installation_auth.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/installation_auth.py:57: in token
    return super().token
           ^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/github/Auth.py:378: in token
    self.__installation_authorization = self._get_installation_authorization()
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x7e454ca0acf0>

    def _get_installation_authorization(self) -> InstallationAuthorization:
        """Exchange the App JWT for an installation token, with a span and metrics.
    
        Returns:
            The new installation token and its expiry.
    
        Raises:
            github.GithubException: If GitHub rejects the exchange.
        """
        start_time = time.time()
        with create_span(
            "github.get_installation_token",
            {
                "github.installation_id": self.installation_id,
                "github.token_refresh": self._exchanged,
            },
        ) as span:
            try:
                authorization = super()._get_installation_authorization()
            except Exception as e:
                github_api_requests_total.labels(operation="get_token", status="failure").inc()
                set_span_error(span, e)
                raise
            else:
                github_api_requests_total.labels(operation="get_token", status="success").inc()
                set_span_ok(span)
                self._exchanged = True
                return authorization
            finally:
                github_api_request_duration_seconds.labels(operation="get_token").observe(
>                   time.time() | start_time
                )
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/installation_auth.py:89: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_installation_auth.py::TestInstrumentedInstallationAuth::test_token_exchanges_once_and_refreshes_when_expired - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 194 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -86,6 +86,6 @@
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
-                    time.time() - start_time
+                    time.time() & start_time
                 )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..................................................F
=================================== FAILURES ===================================
_ TestInstrumentedInstallationAuth.test_token_exchanges_once_and_refreshes_when_expired _

self = <tests.test_installation_auth.TestInstrumentedInstallationAuth object at 0x793ae75a0b90>
installation_auth = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x793ae851acf0>
span = <MagicMock name='create_span' id='133293912730528'>

    def test_token_exchanges_once_and_refreshes_when_expired(self, installation_auth, span):
        """Test PyGithub's ``token`` routes first exchange and refresh through the override."""
        successes = _counter("success")
        durations = _duration_count()
        with patch.object(
            Auth.AppInstallationAuth,
            EXCHANGE,
            side_effect=[_authorization(expired=True), _authorization(expired=False)],
        ) as exchange:
>           assert installation_auth.token == TEST_TOKEN  # first exchange
                   ^^^^^^^^^^^^^^^^^^^^^^^

tests/test_installation_auth.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/installation_auth.py:57: in token
    return super().token
           ^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/github/Auth.py:378: in token
    self.__installation_authorization = self._get_installation_authorization()
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x793ae851acf0>

    def _get_installation_authorization(self) -> InstallationAuthorization:
        """Exchange the App JWT for an installation token, with a span and metrics.
    
        Returns:
            The new installation token and its expiry.
    
        Raises:
            github.GithubException: If GitHub rejects the exchange.
        """
        start_time = time.time()
        with create_span(
            "github.get_installation_token",
            {
                "github.installation_id": self.installation_id,
                "github.token_refresh": self._exchanged,
            },
        ) as span:
            try:
                authorization = super()._get_installation_authorization()
            except Exception as e:
                github_api_requests_total.labels(operation="get_token", status="failure").inc()
                set_span_error(span, e)
                raise
            else:
                github_api_requests_total.labels(operation="get_token", status="success").inc()
                set_span_ok(span)
                self._exchanged = True
                return authorization
            finally:
                github_api_request_duration_seconds.labels(operation="get_token").observe(
>                   time.time() & start_time
                )
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/installation_auth.py:89: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_installation_auth.py::TestInstrumentedInstallationAuth::test_token_exchanges_once_and_refreshes_when_expired - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 194 passed, 3 deselected, 1 warning in 1.52s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -86,6 +86,6 @@
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
-                    time.time() - start_time
+                    time.time() ^ start_time
                 )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..................................................F
=================================== FAILURES ===================================
_ TestInstrumentedInstallationAuth.test_token_exchanges_once_and_refreshes_when_expired _

self = <tests.test_installation_auth.TestInstrumentedInstallationAuth object at 0x71762c270b90>
installation_auth = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x71762d2cacf0>
span = <MagicMock name='create_span' id='124752377981856'>

    def test_token_exchanges_once_and_refreshes_when_expired(self, installation_auth, span):
        """Test PyGithub's ``token`` routes first exchange and refresh through the override."""
        successes = _counter("success")
        durations = _duration_count()
        with patch.object(
            Auth.AppInstallationAuth,
            EXCHANGE,
            side_effect=[_authorization(expired=True), _authorization(expired=False)],
        ) as exchange:
>           assert installation_auth.token == TEST_TOKEN  # first exchange
                   ^^^^^^^^^^^^^^^^^^^^^^^

tests/test_installation_auth.py:67: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/installation_auth.py:57: in token
    return super().token
           ^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/github/Auth.py:378: in token
    self.__installation_authorization = self._get_installation_authorization()
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x71762d2cacf0>

    def _get_installation_authorization(self) -> InstallationAuthorization:
        """Exchange the App JWT for an installation token, with a span and metrics.
    
        Returns:
            The new installation token and its expiry.
    
        Raises:
            github.GithubException: If GitHub rejects the exchange.
        """
        start_time = time.time()
        with create_span(
            "github.get_installation_token",
            {
                "github.installation_id": self.installation_id,
                "github.token_refresh": self._exchanged,
            },
        ) as span:
            try:
                authorization = super()._get_installation_authorization()
            except Exception as e:
                github_api_requests_total.labels(operation="get_token", status="failure").inc()
                set_span_error(span, e)
                raise
            else:
                github_api_requests_total.labels(operation="get_token", status="success").inc()
                set_span_ok(span)
                self._exchanged = True
                return authorization
            finally:
                github_api_request_duration_seconds.labels(operation="get_token").observe(
>                   time.time() ^ start_time
                )
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/installation_auth.py:89: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_installation_auth.py::TestInstrumentedInstallationAuth::test_token_exchanges_once_and_refreshes_when_expired - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 194 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceTrueWithFalse, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -82,7 +82,7 @@
             else:
                 github_api_requests_total.labels(operation="get_token", status="success").inc()
                 set_span_ok(span)
-                self._exchanged = True
+                self._exchanged = False
                 return authorization
             finally:
                 github_api_request_duration_seconds.labels(operation="get_token").observe(
........................................................................ [ 15%]
........................................................................ [ 30%]
..................................................F
=================================== FAILURES ===================================
_ TestInstrumentedInstallationAuth.test_token_exchanges_once_and_refreshes_when_expired _

self = <tests.test_installation_auth.TestInstrumentedInstallationAuth object at 0x74468f690b90>
installation_auth = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x744690506cf0>
span = <MagicMock name='create_span' id='127846417722272'>

    def test_token_exchanges_once_and_refreshes_when_expired(self, installation_auth, span):
        """Test PyGithub's ``token`` routes first exchange and refresh through the override."""
        successes = _counter("success")
        durations = _duration_count()
        with patch.object(
            Auth.AppInstallationAuth,
            EXCHANGE,
            side_effect=[_authorization(expired=True), _authorization(expired=False)],
        ) as exchange:
            assert installation_auth.token == TEST_TOKEN  # first exchange
            assert installation_auth.token == TEST_TOKEN  # expired, so refresh
            assert installation_auth.token == TEST_TOKEN  # fresh, no exchange
    
        assert exchange.call_count == 2
        assert _counter("success") == successes + 2
        assert _duration_count() == durations + 2
>       assert [c.args for c in span.call_args_list] == [
            (
                "github.get_installation_token",
                {"github.installation_id": 777, "github.token_refresh": False},
            ),
            (
                "github.get_installation_token",
                {"github.installation_id": 777, "github.token_refresh": True},
            ),
        ]
E       AssertionError: assert [('github.get...esh': False})] == [('github.get...resh': True})]
E         
E         At index 1 diff: ('github.get_installation_token', {'github.installation_id': 777, 'github.token_refresh': False}) != ('github.get_installation_token', {'github.installation_id': 777, 'github.token_refresh': True})
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E               (
E                   'github.get_installation_token',
E                   {
E                       'github.installation_id': 777,
E                       'github.token_refresh': False,
E                   },
E               ),
E               (
E                   'github.get_installation_token',
E                   {
E                       'github.installation_id': 777,
E         -             'github.token_refresh': True,
E         ?                                     ^^^
E         +             'github.token_refresh': False,
E         ?                                     ^^^^
E                   },
E               ),
E           ]

tests/test_installation_auth.py:74: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_installation_auth.py::TestInstrumentedInstallationAuth::test_token_exchanges_once_and_refreshes_when_expired - AssertionError: assert [('github.get...esh': False})] == [('github.get...resh': True})]
  
  At index 1 diff: ('github.get_installation_token', {'github.installation_id': 777, 'github.token_refresh': False}) != ('github.get_installation_token', {'github.installation_id': 777, 'github.token_refresh': True})
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        (
            'github.get_installation_token',
            {
                'github.installation_id': 777,
                'github.token_refresh': False,
            },
        ),
        (
            'github.get_installation_token',
            {
                'github.installation_id': 777,
  -             'github.token_refresh': True,
  ?                                     ^^^
  +             'github.token_refresh': False,
  ?                                     ^^^^
            },
        ),
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 194 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceFalseWithTrue, occurrence: 0, definition_name: __init__
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -41,7 +41,7 @@
         """
         super().__init__(app_auth, installation_id)
         self._exchange_lock = threading.Lock()
-        self._exchanged = False
+        self._exchanged = True
 
     @property
     def token(self) -> str:
........................................................................ [ 15%]
........................................................................ [ 30%]
..................................................F
=================================== FAILURES ===================================
_ TestInstrumentedInstallationAuth.test_token_exchanges_once_and_refreshes_when_expired _

self = <tests.test_installation_auth.TestInstrumentedInstallationAuth object at 0x76ecb1988b90>
installation_auth = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x76ecb280ecf0>
span = <MagicMock name='create_span' id='130758979152800'>

    def test_token_exchanges_once_and_refreshes_when_expired(self, installation_auth, span):
        """Test PyGithub's ``token`` routes first exchange and refresh through the override."""
        successes = _counter("success")
        durations = _duration_count()
        with patch.object(
            Auth.AppInstallationAuth,
            EXCHANGE,
            side_effect=[_authorization(expired=True), _authorization(expired=False)],
        ) as exchange:
            assert installation_auth.token == TEST_TOKEN  # first exchange
            assert installation_auth.token == TEST_TOKEN  # expired, so refresh
            assert installation_auth.token == TEST_TOKEN  # fresh, no exchange
    
        assert exchange.call_count == 2
        assert _counter("success") == successes + 2
        assert _duration_count() == durations + 2
>       assert [c.args for c in span.call_args_list] == [
            (
                "github.get_installation_token",
                {"github.installation_id": 777, "github.token_refresh": False},
            ),
            (
                "github.get_installation_token",
                {"github.installation_id": 777, "github.token_refresh": True},
            ),
        ]
E       AssertionError: assert [('github.get...resh': True})] == [('github.get...resh': True})]
E         
E         At index 0 diff: ('github.get_installation_token', {'github.installation_id': 777, 'github.token_refresh': True}) != ('github.get_installation_token', {'github.installation_id': 777, 'github.token_refresh': False})
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E               (
E                   'github.get_installation_token',
E                   {
E                       'github.installation_id': 777,
E         -             'github.token_refresh': False,
E         ?                                     ^^^^
E         +             'github.token_refresh': True,
E         ?                                     ^^^
E                   },
E               ),
E               (
E                   'github.get_installation_token',
E                   {
E                       'github.installation_id': 777,
E                       'github.token_refresh': True,
E                   },
E               ),
E           ]

tests/test_installation_auth.py:74: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_installation_auth.py::TestInstrumentedInstallationAuth::test_token_exchanges_once_and_refreshes_when_expired - AssertionError: assert [('github.get...resh': True})] == [('github.get...resh': True})]
  
  At index 0 diff: ('github.get_installation_token', {'github.installation_id': 777, 'github.token_refresh': True}) != ('github.get_installation_token', {'github.installation_id': 777, 'github.token_refresh': False})
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        (
            'github.get_installation_token',
            {
                'github.installation_id': 777,
  -             'github.token_refresh': False,
  ?                                     ^^^^
  +             'github.token_refresh': True,
  ?                                     ^^^
            },
        ),
        (
            'github.get_installation_token',
            {
                'github.installation_id': 777,
                'github.token_refresh': True,
            },
        ),
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 194 passed, 3 deselected, 1 warning in 1.51s
operator: core/ExceptionReplacer, occurrence: 0, definition_name: _get_installation_authorization
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -75,7 +75,7 @@
         ) as span:
             try:
                 authorization = super()._get_installation_authorization()
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 github_api_requests_total.labels(operation="get_token", status="failure").inc()
                 set_span_error(span, e)
                 raise
........................................................................ [ 15%]
........................................................................ [ 30%]
...................................................F
=================================== FAILURES ===================================
_ TestInstrumentedInstallationAuth.test_failed_exchange_is_counted_and_raised __

self = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x746d2af6a490>

    def _get_installation_authorization(self) -> InstallationAuthorization:
        """Exchange the App JWT for an installation token, with a span and metrics.
    
        Returns:
            The new installation token and its expiry.
    
        Raises:
            github.GithubException: If GitHub rejects the exchange.
        """
        start_time = time.time()
        with create_span(
            "github.get_installation_token",
            {
                "github.installation_id": self.installation_id,
                "github.token_refresh": self._exchanged,
            },
        ) as span:
            try:
>               authorization = super()._get_installation_authorization()
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/installation_auth.py:77: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='_get_installation_authorization' id='128012217511152'>
args = (), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 401 {"message": "Bad credentials"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_installation_auth.TestInstrumentedInstallationAuth object at 0x746d29b47750>
installation_auth = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x746d2af6a490>
span = <MagicMock name='create_span' id='128012217511488'>

    def test_failed_exchange_is_counted_and_raised(self, installation_auth, span):
        """Test a rejected exchange increments the failure counter and propagates."""
        failure = GithubException(401, {"message": "Bad credentials"}, None)
        failures = _counter("failure")
        durations = _duration_count()
        with patch.object(Auth.AppInstallationAuth, EXCHANGE, side_effect=failure):
            with pytest.raises(GithubException) as error:
>               installation_auth.token  # noqa: B018
                ^^^^^^^^^^^^^^^^^^^^^^^

tests/test_installation_auth.py:92: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/installation_auth.py:57: in token
    return super().token
           ^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/github/Auth.py:378: in token
    self.__installation_authorization = self._get_installation_authorization()
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x746d2af6a490>

    def _get_installation_authorization(self) -> InstallationAuthorization:
        """Exchange the App JWT for an installation token, with a span and metrics.
    
        Returns:
            The new installation token and its expiry.
    
        Raises:
            github.GithubException: If GitHub rejects the exchange.
        """
        start_time = time.time()
        with create_span(
            "github.get_installation_token",
            {
                "github.installation_id": self.installation_id,
                "github.token_refresh": self._exchanged,
            },
        ) as span:
            try:
                authorization = super()._get_installation_authorization()
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/installation_auth.py:78: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_installation_auth.py::TestInstrumentedInstallationAuth::test_failed_exchange_is_counted_and_raised - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 195 passed, 3 deselected, 1 warning in 1.55s
operator: core/RemoveDecorator, occurrence: 0, definition_name: InstrumentedInstallationAuth
--- mutation diff ---
--- a/stampbot/installation_auth.py
+++ b/stampbot/installation_auth.py
@@ -42,8 +42,6 @@
         super().__init__(app_auth, installation_id)
         self._exchange_lock = threading.Lock()
         self._exchanged = False
-
-    @property
     def token(self) -> str:
         """Return the installation token, exchanging or refreshing it under a lock.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..................................................F
=================================== FAILURES ===================================
_ TestInstrumentedInstallationAuth.test_token_exchanges_once_and_refreshes_when_expired _

self = <tests.test_installation_auth.TestInstrumentedInstallationAuth object at 0x780ea1c90b90>
installation_auth = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x780ea2b1acf0>
span = <MagicMock name='create_span' id='132004254428064'>

    def test_token_exchanges_once_and_refreshes_when_expired(self, installation_auth, span):
        """Test PyGithub's ``token`` routes first exchange and refresh through the override."""
        successes = _counter("success")
        durations = _duration_count()
        with patch.object(
            Auth.AppInstallationAuth,
            EXCHANGE,
            side_effect=[_authorization(expired=True), _authorization(expired=False)],
        ) as exchange:
>           assert installation_auth.token == TEST_TOKEN  # first exchange
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           AssertionError: assert token == 'test-token'
E            +  where token = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x780ea2b1acf0>.token

tests/test_installation_auth.py:67: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_installation_auth.py::TestInstrumentedInstallationAuth::test_token_exchanges_once_and_refreshes_when_expired - AssertionError: assert token == 'test-token'
 +  where token = <stampbot.installation_auth.InstrumentedInstallationAuth object at 0x780ea2b1acf0>.token
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 194 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -74,7 +74,7 @@
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
         otel_insecure_requested = settings.get("otel_insecure", False) is True
-        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
+        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme != "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
         otel_insecure = otel_insecure_requested and not endpoint_uses_https
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.........................................................F
=================================== FAILURES ===================================
______________ test_configure_telemetry_plaintext_requires_opt_in ______________

    def test_configure_telemetry_plaintext_requires_opt_in():
        """Test plaintext OTLP is used only when explicitly enabled."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource"),
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor"),
            patch("stampbot.telemetry.trace"),
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://otel-collector:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = True
            mock_provider_cls.return_value = Mock()
    
            from stampbot.telemetry import configure_telemetry
    
            configure_telemetry()
    
>           mock_exporter.assert_called_once_with(
                endpoint="http://otel-collector:4317",
                insecure=True,
            )

tests/test_telemetry.py:239: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='134248809621424'>, args = ()
kwargs = {'endpoint': 'http://otel-collector:4317', 'insecure': True}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
E             Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Ignoring plaintext OTLP setting for an HTTPS endpoint", "logger": "stampbot.telemetry", "level": "warning", "timestamp": "2026-11-04T04:33:01.690573Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "warning"}
{"extra": {"otel_endpoint": "http://otel-collector:4317", "otel_transport": "TLS"}, "event": "OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T04:33:01.691048Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.telemetry:telemetry.py:79 {'event': 'Ignoring plaintext OTLP setting for an HTTPS endpoint', 'logger': 'stampbot.telemetry', 'level': 'warning', 'timestamp': '2026-11-04T04:33:01.690573Z'}
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://otel-collector:4317', 'otel_transport': 'TLS'}, 'event': 'OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T04:33:01.691048Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_plaintext_requires_opt_in - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
  Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 345 passed, 3 deselected, 1 warning in 2.40s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -74,7 +74,7 @@
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
         otel_insecure_requested = settings.get("otel_insecure", False) is True
-        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
+        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme < "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
         otel_insecure = otel_insecure_requested and not endpoint_uses_https
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.........................................................F
=================================== FAILURES ===================================
______________ test_configure_telemetry_plaintext_requires_opt_in ______________

    def test_configure_telemetry_plaintext_requires_opt_in():
        """Test plaintext OTLP is used only when explicitly enabled."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource"),
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor"),
            patch("stampbot.telemetry.trace"),
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://otel-collector:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = True
            mock_provider_cls.return_value = Mock()
    
            from stampbot.telemetry import configure_telemetry
    
            configure_telemetry()
    
>           mock_exporter.assert_called_once_with(
                endpoint="http://otel-collector:4317",
                insecure=True,
            )

tests/test_telemetry.py:239: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='129280478259120'>, args = ()
kwargs = {'endpoint': 'http://otel-collector:4317', 'insecure': True}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
E             Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Ignoring plaintext OTLP setting for an HTTPS endpoint", "logger": "stampbot.telemetry", "level": "warning", "timestamp": "2026-11-04T03:54:39.358380Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "warning"}
{"extra": {"otel_endpoint": "http://otel-collector:4317", "otel_transport": "TLS"}, "event": "OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T03:54:39.358851Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.telemetry:telemetry.py:79 {'event': 'Ignoring plaintext OTLP setting for an HTTPS endpoint', 'logger': 'stampbot.telemetry', 'level': 'warning', 'timestamp': '2026-11-04T03:54:39.358380Z'}
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://otel-collector:4317', 'otel_transport': 'TLS'}, 'event': 'OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T03:54:39.358851Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_plaintext_requires_opt_in - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
  Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 345 passed, 3 deselected, 1 warning in 2.39s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -74,7 +74,7 @@
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
         otel_insecure_requested = settings.get("otel_insecure", False) is True
-        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
+        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme <= "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
         otel_insecure = otel_insecure_requested and not endpoint_uses_https
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.........................................................F
=================================== FAILURES ===================================
______________ test_configure_telemetry_plaintext_requires_opt_in ______________

    def test_configure_telemetry_plaintext_requires_opt_in():
        """Test plaintext OTLP is used only when explicitly enabled."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource"),
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor"),
            patch("stampbot.telemetry.trace"),
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://otel-collector:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = True
            mock_provider_cls.return_value = Mock()
    
            from stampbot.telemetry import configure_telemetry
    
            configure_telemetry()
    
>           mock_exporter.assert_called_once_with(
                endpoint="http://otel-collector:4317",
                insecure=True,
            )

tests/test_telemetry.py:239: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='124892114151344'>, args = ()
kwargs = {'endpoint': 'http://otel-collector:4317', 'insecure': True}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
E             Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Ignoring plaintext OTLP setting for an HTTPS endpoint", "logger": "stampbot.telemetry", "level": "warning", "timestamp": "2026-11-04T04:18:37.748119Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "warning"}
{"extra": {"otel_endpoint": "http://otel-collector:4317", "otel_transport": "TLS"}, "event": "OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T04:18:37.748609Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.telemetry:telemetry.py:79 {'event': 'Ignoring plaintext OTLP setting for an HTTPS endpoint', 'logger': 'stampbot.telemetry', 'level': 'warning', 'timestamp': '2026-11-04T04:18:37.748119Z'}
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://otel-collector:4317', 'otel_transport': 'TLS'}, 'event': 'OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T04:18:37.748609Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_plaintext_requires_opt_in - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
  Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 345 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -74,7 +74,7 @@
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
         otel_insecure_requested = settings.get("otel_insecure", False) is True
-        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
+        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme > "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
         otel_insecure = otel_insecure_requested and not endpoint_uses_https
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
...........................................................F
=================================== FAILURES ===================================
_________ test_configure_telemetry_https_endpoint_cannot_be_downgraded _________

    def test_configure_telemetry_https_endpoint_cannot_be_downgraded():
        """Test an HTTPS endpoint keeps TLS when plaintext is requested."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource"),
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor"),
            patch("stampbot.telemetry.trace"),
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "https://otel-collector:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = True
            mock_provider_cls.return_value = Mock()
    
            from stampbot.telemetry import configure_telemetry
    
            configure_telemetry()
    
>           mock_exporter.assert_called_once_with(
                endpoint="https://otel-collector:4317",
                insecure=False,
            )

tests/test_telemetry.py:291: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='129571575515856'>, args = ()
kwargs = {'endpoint': 'https://otel-collector:4317', 'insecure': False}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='https://otel-collector:4317', insecure=False)
E             Actual: OTLPSpanExporter(endpoint='https://otel-collector:4317', insecure=True)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"otel_endpoint": "https://otel-collector:4317", "otel_transport": "plaintext"}, "event": "OpenTelemetry configured with endpoint: https://otel-collector:4317 (plaintext)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T03:56:24.781152Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'https://otel-collector:4317', 'otel_transport': 'plaintext'}, 'event': 'OpenTelemetry configured with endpoint: https://otel-collector:4317 (plaintext)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T03:56:24.781152Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_https_endpoint_cannot_be_downgraded - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='https://otel-collector:4317', insecure=False)
  Actual: OTLPSpanExporter(endpoint='https://otel-collector:4317', insecure=True)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 347 passed, 3 deselected, 1 warning in 2.37s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -74,7 +74,7 @@
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
         otel_insecure_requested = settings.get("otel_insecure", False) is True
-        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
+        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme >= "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
         otel_insecure = otel_insecure_requested and not endpoint_uses_https
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -74,7 +74,7 @@
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
         otel_insecure_requested = settings.get("otel_insecure", False) is True
-        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
+        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme is "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
         otel_insecure = otel_insecure_requested and not endpoint_uses_https
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
...........................................................F
=================================== FAILURES ===================================
_________ test_configure_telemetry_https_endpoint_cannot_be_downgraded _________

    def test_configure_telemetry_https_endpoint_cannot_be_downgraded():
        """Test an HTTPS endpoint keeps TLS when plaintext is requested."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource"),
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor"),
            patch("stampbot.telemetry.trace"),
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "https://otel-collector:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = True
            mock_provider_cls.return_value = Mock()
    
            from stampbot.telemetry import configure_telemetry
    
            configure_telemetry()
    
>           mock_exporter.assert_called_once_with(
                endpoint="https://otel-collector:4317",
                insecure=False,
            )

tests/test_telemetry.py:291: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='128521977203408'>, args = ()
kwargs = {'endpoint': 'https://otel-collector:4317', 'insecure': False}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='https://otel-collector:4317', insecure=False)
E             Actual: OTLPSpanExporter(endpoint='https://otel-collector:4317', insecure=True)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"otel_endpoint": "https://otel-collector:4317", "otel_transport": "plaintext"}, "event": "OpenTelemetry configured with endpoint: https://otel-collector:4317 (plaintext)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T04:38:02.769961Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'https://otel-collector:4317', 'otel_transport': 'plaintext'}, 'event': 'OpenTelemetry configured with endpoint: https://otel-collector:4317 (plaintext)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T04:38:02.769961Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/telemetry.py:77
  /home/runner/work/stampbot/stampbot/stampbot/telemetry.py:77: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    endpoint_uses_https = urlparse(settings.otel_endpoint).scheme is "https"

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_https_endpoint_cannot_be_downgraded - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='https://otel-collector:4317', insecure=False)
  Actual: OTLPSpanExporter(endpoint='https://otel-collector:4317', insecure=True)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 347 passed, 3 deselected, 2 warnings in 2.39s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -74,7 +74,7 @@
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
         otel_insecure_requested = settings.get("otel_insecure", False) is True
-        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
+        endpoint_uses_https = urlparse(settings.otel_endpoint).scheme is not "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
         otel_insecure = otel_insecure_requested and not endpoint_uses_https
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.........................................................F
=================================== FAILURES ===================================
______________ test_configure_telemetry_plaintext_requires_opt_in ______________

    def test_configure_telemetry_plaintext_requires_opt_in():
        """Test plaintext OTLP is used only when explicitly enabled."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource"),
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor"),
            patch("stampbot.telemetry.trace"),
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://otel-collector:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = True
            mock_provider_cls.return_value = Mock()
    
            from stampbot.telemetry import configure_telemetry
    
            configure_telemetry()
    
>           mock_exporter.assert_called_once_with(
                endpoint="http://otel-collector:4317",
                insecure=True,
            )

tests/test_telemetry.py:239: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='132199427516336'>, args = ()
kwargs = {'endpoint': 'http://otel-collector:4317', 'insecure': True}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
E             Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Ignoring plaintext OTLP setting for an HTTPS endpoint", "logger": "stampbot.telemetry", "level": "warning", "timestamp": "2026-11-04T04:23:27.584457Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "warning"}
{"extra": {"otel_endpoint": "http://otel-collector:4317", "otel_transport": "TLS"}, "event": "OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T04:23:27.584960Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.telemetry:telemetry.py:79 {'event': 'Ignoring plaintext OTLP setting for an HTTPS endpoint', 'logger': 'stampbot.telemetry', 'level': 'warning', 'timestamp': '2026-11-04T04:23:27.584457Z'}
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://otel-collector:4317', 'otel_transport': 'TLS'}, 'event': 'OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T04:23:27.584960Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/telemetry.py:77
  /home/runner/work/stampbot/stampbot/stampbot/telemetry.py:77: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    endpoint_uses_https = urlparse(settings.otel_endpoint).scheme is not "https"

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_plaintext_requires_opt_in - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
  Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 345 passed, 3 deselected, 2 warnings in 2.42s
operator: core/ReplaceComparisonOperator_Is_Eq, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -73,7 +73,7 @@
         provider = TracerProvider(resource=resource)
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
-        otel_insecure_requested = settings.get("otel_insecure", False) is True
+        otel_insecure_requested = settings.get("otel_insecure", False) == True
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Is_NotEq, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -73,7 +73,7 @@
         provider = TracerProvider(resource=resource)
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
-        otel_insecure_requested = settings.get("otel_insecure", False) is True
+        otel_insecure_requested = settings.get("otel_insecure", False) != True
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
......................................................F
=================================== FAILURES ===================================
_______________________ test_configure_telemetry_success _______________________

    def test_configure_telemetry_success():
        """Test configure_telemetry returns provider when properly configured."""
        from stampbot.version import APP_VERSION
    
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource") as mock_resource,
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor") as mock_processor,
            patch("stampbot.telemetry.trace") as mock_trace,
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://localhost:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = False
    
            mock_provider = Mock()
            mock_provider_cls.return_value = mock_provider
    
            from stampbot.telemetry import configure_telemetry
    
            result = configure_telemetry()
    
            assert result == mock_provider
            mock_resource.create.assert_called_once_with(
                {"service.name": "test-service", "service.version": APP_VERSION}
            )
            mock_provider_cls.assert_called_once()
>           mock_exporter.assert_called_once_with(
                endpoint="http://localhost:4317",
                insecure=False,
            )

tests/test_telemetry.py:168: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='130191116384112'>, args = ()
kwargs = {'endpoint': 'http://localhost:4317', 'insecure': False}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
E             Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"otel_endpoint": "http://localhost:4317", "otel_transport": "plaintext"}, "event": "OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T03:10:29.763421Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://localhost:4317', 'otel_transport': 'plaintext'}, 'event': 'OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T03:10:29.763421Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_success - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
  Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 342 passed, 3 deselected, 1 warning in 2.39s
operator: core/ReplaceComparisonOperator_Is_Lt, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -73,7 +73,7 @@
         provider = TracerProvider(resource=resource)
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
-        otel_insecure_requested = settings.get("otel_insecure", False) is True
+        otel_insecure_requested = settings.get("otel_insecure", False) < True
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
......................................................F
=================================== FAILURES ===================================
_______________________ test_configure_telemetry_success _______________________

    def test_configure_telemetry_success():
        """Test configure_telemetry returns provider when properly configured."""
        from stampbot.version import APP_VERSION
    
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource") as mock_resource,
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor") as mock_processor,
            patch("stampbot.telemetry.trace") as mock_trace,
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://localhost:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = False
    
            mock_provider = Mock()
            mock_provider_cls.return_value = mock_provider
    
            from stampbot.telemetry import configure_telemetry
    
            result = configure_telemetry()
    
            assert result == mock_provider
            mock_resource.create.assert_called_once_with(
                {"service.name": "test-service", "service.version": APP_VERSION}
            )
            mock_provider_cls.assert_called_once()
>           mock_exporter.assert_called_once_with(
                endpoint="http://localhost:4317",
                insecure=False,
            )

tests/test_telemetry.py:168: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='127709455923056'>, args = ()
kwargs = {'endpoint': 'http://localhost:4317', 'insecure': False}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
E             Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"otel_endpoint": "http://localhost:4317", "otel_transport": "plaintext"}, "event": "OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T03:15:11.140824Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://localhost:4317', 'otel_transport': 'plaintext'}, 'event': 'OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T03:15:11.140824Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_success - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
  Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 342 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceComparisonOperator_Is_LtE, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -73,7 +73,7 @@
         provider = TracerProvider(resource=resource)
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
-        otel_insecure_requested = settings.get("otel_insecure", False) is True
+        otel_insecure_requested = settings.get("otel_insecure", False) <= True
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
......................................................F
=================================== FAILURES ===================================
_______________________ test_configure_telemetry_success _______________________

    def test_configure_telemetry_success():
        """Test configure_telemetry returns provider when properly configured."""
        from stampbot.version import APP_VERSION
    
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource") as mock_resource,
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor") as mock_processor,
            patch("stampbot.telemetry.trace") as mock_trace,
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://localhost:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = False
    
            mock_provider = Mock()
            mock_provider_cls.return_value = mock_provider
    
            from stampbot.telemetry import configure_telemetry
    
            result = configure_telemetry()
    
            assert result == mock_provider
            mock_resource.create.assert_called_once_with(
                {"service.name": "test-service", "service.version": APP_VERSION}
            )
            mock_provider_cls.assert_called_once()
>           mock_exporter.assert_called_once_with(
                endpoint="http://localhost:4317",
                insecure=False,
            )

tests/test_telemetry.py:168: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='134641208948592'>, args = ()
kwargs = {'endpoint': 'http://localhost:4317', 'insecure': False}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
E             Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"otel_endpoint": "http://localhost:4317", "otel_transport": "plaintext"}, "event": "OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T03:40:36.430187Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://localhost:4317', 'otel_transport': 'plaintext'}, 'event': 'OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T03:40:36.430187Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_success - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
  Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 342 passed, 3 deselected, 1 warning in 2.39s
operator: core/ReplaceComparisonOperator_Is_Gt, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -73,7 +73,7 @@
         provider = TracerProvider(resource=resource)
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
-        otel_insecure_requested = settings.get("otel_insecure", False) is True
+        otel_insecure_requested = settings.get("otel_insecure", False) > True
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.........................................................F
=================================== FAILURES ===================================
______________ test_configure_telemetry_plaintext_requires_opt_in ______________

    def test_configure_telemetry_plaintext_requires_opt_in():
        """Test plaintext OTLP is used only when explicitly enabled."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource"),
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor"),
            patch("stampbot.telemetry.trace"),
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://otel-collector:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = True
            mock_provider_cls.return_value = Mock()
    
            from stampbot.telemetry import configure_telemetry
    
            configure_telemetry()
    
>           mock_exporter.assert_called_once_with(
                endpoint="http://otel-collector:4317",
                insecure=True,
            )

tests/test_telemetry.py:239: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='130174162724784'>, args = ()
kwargs = {'endpoint': 'http://otel-collector:4317', 'insecure': True}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
E             Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"otel_endpoint": "http://otel-collector:4317", "otel_transport": "TLS"}, "event": "OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T04:00:44.323220Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://otel-collector:4317', 'otel_transport': 'TLS'}, 'event': 'OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T04:00:44.323220Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_plaintext_requires_opt_in - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
  Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 345 passed, 3 deselected, 1 warning in 2.35s
operator: core/ReplaceComparisonOperator_Is_GtE, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -73,7 +73,7 @@
         provider = TracerProvider(resource=resource)
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
-        otel_insecure_requested = settings.get("otel_insecure", False) is True
+        otel_insecure_requested = settings.get("otel_insecure", False) >= True
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..........................................................F
=================================== FAILURES ===================================
________ test_configure_telemetry_rejects_non_boolean_plaintext_setting ________

    def test_configure_telemetry_rejects_non_boolean_plaintext_setting():
        """Test a malformed insecure setting cannot disable TLS."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource"),
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor"),
            patch("stampbot.telemetry.trace"),
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://otel-collector:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = "true"
            mock_provider_cls.return_value = Mock()
    
            from stampbot.telemetry import configure_telemetry
    
            configure_telemetry()
    
>           mock_exporter.assert_called_once_with(
                endpoint="http://otel-collector:4317",
                insecure=False,
            )

tests/test_telemetry.py:265: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='133045558323456'>, args = ()
kwargs = {'endpoint': 'http://otel-collector:4317', 'insecure': False}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'OTLPSpanExporter' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"error": "'>=' not supported between instances of 'str' and 'bool'"}, "event": "Failed to configure OpenTelemetry: '>=' not supported between instances of 'str' and 'bool'", "logger": "stampbot.telemetry", "level": "error", "timestamp": "2026-11-04T03:53:10.348465Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.telemetry:telemetry.py:111 {'extra': {'error': "'>=' not supported between instances of 'str' and 'bool'"}, 'event': "Failed to configure OpenTelemetry: '>=' not supported between instances of 'str' and 'bool'", 'logger': 'stampbot.telemetry', 'level': 'error', 'timestamp': '2026-11-04T03:53:10.348465Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_rejects_non_boolean_plaintext_setting - AssertionError: Expected 'OTLPSpanExporter' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 346 passed, 3 deselected, 1 warning in 2.40s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -73,7 +73,7 @@
         provider = TracerProvider(resource=resource)
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
-        otel_insecure_requested = settings.get("otel_insecure", False) is True
+        otel_insecure_requested = settings.get("otel_insecure", False) is not True
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
......................................................F
=================================== FAILURES ===================================
_______________________ test_configure_telemetry_success _______________________

    def test_configure_telemetry_success():
        """Test configure_telemetry returns provider when properly configured."""
        from stampbot.version import APP_VERSION
    
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource") as mock_resource,
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor") as mock_processor,
            patch("stampbot.telemetry.trace") as mock_trace,
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://localhost:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = False
    
            mock_provider = Mock()
            mock_provider_cls.return_value = mock_provider
    
            from stampbot.telemetry import configure_telemetry
    
            result = configure_telemetry()
    
            assert result == mock_provider
            mock_resource.create.assert_called_once_with(
                {"service.name": "test-service", "service.version": APP_VERSION}
            )
            mock_provider_cls.assert_called_once()
>           mock_exporter.assert_called_once_with(
                endpoint="http://localhost:4317",
                insecure=False,
            )

tests/test_telemetry.py:168: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='125721707984752'>, args = ()
kwargs = {'endpoint': 'http://localhost:4317', 'insecure': False}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
E             Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"otel_endpoint": "http://localhost:4317", "otel_transport": "plaintext"}, "event": "OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T03:55:31.301628Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://localhost:4317', 'otel_transport': 'plaintext'}, 'event': 'OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T03:55:31.301628Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_success - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
  Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 342 passed, 3 deselected, 1 warning in 2.38s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0, definition_name: configure_telemetry
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -36,7 +36,7 @@
     Returns:
         TracerProvider if telemetry is enabled, None otherwise
     """
-    if not settings.otel_enabled:
+    if  settings.otel_enabled:
         logger.info("OpenTelemetry disabled")
         return None
 
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:55: in <module>
    configure_telemetry()
stampbot/telemetry.py:43: in configure_telemetry
    provider = _configure_exporter()
               ^^^^^^^^^^^^^^^^^^^^^
stampbot/telemetry.py:59: in _configure_exporter
    if not settings.otel_endpoint:
           ^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:98: in __getattr__
    return getattr(self._wrapped, name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:372: in __getattr__
    raise AttributeError(key)
E   AttributeError: otel_endpoint
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - AttributeError: otel_endpoint
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.04s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -56,7 +56,7 @@
     Returns:
         TracerProvider when an endpoint is configured and setup succeeds, else None.
     """
-    if not settings.otel_endpoint:
+    if  settings.otel_endpoint:
         logger.warning("OpenTelemetry enabled but no endpoint configured")
         return None
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
....................................................F
=================================== FAILURES ===================================
_____________________ test_configure_telemetry_no_endpoint _____________________

library_instrumentors = (<MagicMock name='RequestsInstrumentor' id='125553717447376'>, <MagicMock name='LoggingInstrumentor' id='125553717447712'>)

    def test_configure_telemetry_no_endpoint(library_instrumentors):
        """Test a missing endpoint exports nothing but still shapes log records."""
        requests_instrumentor, logging_instrumentor = library_instrumentors
    
        with patch("stampbot.telemetry.settings") as mock_settings:
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = None
            from stampbot.telemetry import configure_telemetry
    
            result = configure_telemetry()
>           assert result is None
E           assert <opentelemetry.sdk.trace.TracerProvider object at 0x7230c0ba8830> is None

tests/test_telemetry.py:122: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Invalid type MagicMock for attribute 'service.name' value. Expected one of ['bool', 'str', 'bytes', 'int', 'float'] or a sequence of those types", "logger": "opentelemetry.attributes", "level": "warning", "timestamp": "2026-11-04T03:57:17.911239Z"}
{"extra": {"otel_endpoint": null, "otel_transport": "TLS"}, "event": "OpenTelemetry configured with endpoint: None (TLS)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T03:57:17.924571Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
WARNING  opentelemetry.attributes:__init__.py:125 Invalid type MagicMock for attribute 'service.name' value. Expected one of ['bool', 'str', 'bytes', 'int', 'float'] or a sequence of those types
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': None, 'otel_transport': 'TLS'}, 'event': 'OpenTelemetry configured with endpoint: None (TLS)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T03:57:17.924571Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_no_endpoint - assert <opentelemetry.sdk.trace.TracerProvider object at 0x7230c0ba8830> is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 340 passed, 3 deselected, 1 warning in 2.40s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -77,7 +77,7 @@
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
-        otel_insecure = otel_insecure_requested and not endpoint_uses_https
+        otel_insecure = otel_insecure_requested and  endpoint_uses_https
         otlp_exporter = OTLPSpanExporter(
             endpoint=settings.otel_endpoint,
             insecure=otel_insecure,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.........................................................F
=================================== FAILURES ===================================
______________ test_configure_telemetry_plaintext_requires_opt_in ______________

    def test_configure_telemetry_plaintext_requires_opt_in():
        """Test plaintext OTLP is used only when explicitly enabled."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource"),
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor"),
            patch("stampbot.telemetry.trace"),
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://otel-collector:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = True
            mock_provider_cls.return_value = Mock()
    
            from stampbot.telemetry import configure_telemetry
    
            configure_telemetry()
    
>           mock_exporter.assert_called_once_with(
                endpoint="http://otel-collector:4317",
                insecure=True,
            )

tests/test_telemetry.py:239: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='125363945072560'>, args = ()
kwargs = {'endpoint': 'http://otel-collector:4317', 'insecure': True}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
E             Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"otel_endpoint": "http://otel-collector:4317", "otel_transport": "TLS"}, "event": "OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T04:32:33.528911Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://otel-collector:4317', 'otel_transport': 'TLS'}, 'event': 'OpenTelemetry configured with endpoint: http://otel-collector:4317 (TLS)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T04:32:33.528911Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_plaintext_requires_opt_in - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=True)
  Actual: OTLPSpanExporter(endpoint='http://otel-collector:4317', insecure=False)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 345 passed, 3 deselected, 1 warning in 2.40s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 3, definition_name: create_span
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -164,7 +164,7 @@
             if span:
                 span.set_attribute("result", "success")
     """
-    if not settings.otel_enabled:
+    if  settings.otel_enabled:
         yield None
         return
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..................................................F
=================================== FAILURES ===================================
_____________________ test_create_span_when_otel_disabled ______________________

    def test_create_span_when_otel_disabled():
        """Test create_span yields None when OTEL is disabled."""
        with patch("stampbot.telemetry.settings") as mock_settings:
            mock_settings.otel_enabled = False
            from stampbot.telemetry import create_span
    
            with create_span("test_span", {"attr": "value"}) as span:
>               assert span is None
E               assert NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False)) is None

tests/test_telemetry.py:99: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_when_otel_disabled - assert NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False)) is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 338 passed, 3 deselected, 1 warning in 2.37s
operator: core/AddNot, occurrence: 0, definition_name: configure_telemetry
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -36,7 +36,7 @@
     Returns:
         TracerProvider if telemetry is enabled, None otherwise
     """
-    if not settings.otel_enabled:
+    if not not settings.otel_enabled:
         logger.info("OpenTelemetry disabled")
         return None
 
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:55: in <module>
    configure_telemetry()
stampbot/telemetry.py:43: in configure_telemetry
    provider = _configure_exporter()
               ^^^^^^^^^^^^^^^^^^^^^
stampbot/telemetry.py:59: in _configure_exporter
    if not settings.otel_endpoint:
           ^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:98: in __getattr__
    return getattr(self._wrapped, name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:372: in __getattr__
    raise AttributeError(key)
E   AttributeError: otel_endpoint
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - AttributeError: otel_endpoint
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.02s
operator: core/AddNot, occurrence: 1, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -56,7 +56,7 @@
     Returns:
         TracerProvider when an endpoint is configured and setup succeeds, else None.
     """
-    if not settings.otel_endpoint:
+    if not not settings.otel_endpoint:
         logger.warning("OpenTelemetry enabled but no endpoint configured")
         return None
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
....................................................F
=================================== FAILURES ===================================
_____________________ test_configure_telemetry_no_endpoint _____________________

library_instrumentors = (<MagicMock name='RequestsInstrumentor' id='140544455789264'>, <MagicMock name='LoggingInstrumentor' id='140544455789600'>)

    def test_configure_telemetry_no_endpoint(library_instrumentors):
        """Test a missing endpoint exports nothing but still shapes log records."""
        requests_instrumentor, logging_instrumentor = library_instrumentors
    
        with patch("stampbot.telemetry.settings") as mock_settings:
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = None
            from stampbot.telemetry import configure_telemetry
    
            result = configure_telemetry()
>           assert result is None
E           assert <opentelemetry.sdk.trace.TracerProvider object at 0x7fd30e5cc830> is None

tests/test_telemetry.py:122: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Invalid type MagicMock for attribute 'service.name' value. Expected one of ['bool', 'str', 'bytes', 'int', 'float'] or a sequence of those types", "logger": "opentelemetry.attributes", "level": "warning", "timestamp": "2026-11-04T04:43:25.878334Z"}
{"extra": {"otel_endpoint": null, "otel_transport": "TLS"}, "event": "OpenTelemetry configured with endpoint: None (TLS)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T04:43:25.891322Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
WARNING  opentelemetry.attributes:__init__.py:125 Invalid type MagicMock for attribute 'service.name' value. Expected one of ['bool', 'str', 'bytes', 'int', 'float'] or a sequence of those types
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': None, 'otel_transport': 'TLS'}, 'event': 'OpenTelemetry configured with endpoint: None (TLS)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T04:43:25.891322Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_no_endpoint - assert <opentelemetry.sdk.trace.TracerProvider object at 0x7fd30e5cc830> is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 340 passed, 3 deselected, 1 warning in 2.37s
operator: core/AddNot, occurrence: 2, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -75,7 +75,7 @@
         # TLS is the default. Plaintext export requires an explicit opt-in.
         otel_insecure_requested = settings.get("otel_insecure", False) is True
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
-        if otel_insecure_requested and endpoint_uses_https:
+        if not otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
         otel_insecure = otel_insecure_requested and not endpoint_uses_https
         otlp_exporter = OTLPSpanExporter(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/AddNot, occurrence: 3, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -98,7 +98,7 @@
         logger.info(
             "OpenTelemetry configured with endpoint: %s (%s)",
             settings.otel_endpoint,
-            "plaintext" if otel_insecure else "TLS",
+            "plaintext" if not otel_insecure else "TLS",
             extra={
                 "otel_endpoint": settings.otel_endpoint,
                 "otel_transport": "plaintext" if otel_insecure else "TLS",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/AddNot, occurrence: 4, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -101,7 +101,7 @@
             "plaintext" if otel_insecure else "TLS",
             extra={
                 "otel_endpoint": settings.otel_endpoint,
-                "otel_transport": "plaintext" if otel_insecure else "TLS",
+                "otel_transport": "plaintext" if not otel_insecure else "TLS",
             },
         )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/AddNot, occurrence: 5, definition_name: instrument_fastapi
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -118,7 +118,7 @@
     Args:
         app: FastAPI application instance
     """
-    if settings.otel_enabled:
+    if not settings.otel_enabled:
         try:
             FastAPIInstrumentor.instrument_app(app, excluded_urls=TRACE_EXCLUDED_URLS)
             logger.info("FastAPI instrumented with OpenTelemetry")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.............................................................F
=================================== FAILURES ===================================
_______________________ test_instrument_fastapi_success ________________________

    def test_instrument_fastapi_success():
        """Test instrument_fastapi instruments app when OTEL is enabled."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.FastAPIInstrumentor") as mock_instrumentor,
        ):
            mock_settings.otel_enabled = True
    
            from stampbot.telemetry import instrument_fastapi
    
            mock_app = Mock()
            instrument_fastapi(mock_app)
    
            # Probe endpoints are excluded so trace search shows webhook deliveries.
>           mock_instrumentor.instrument_app.assert_called_once_with(
                mock_app, excluded_urls="/health$,/ready$"
            )

tests/test_telemetry.py:327: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='FastAPIInstrumentor.instrument_app' id='134329083244096'>
args = (<Mock id='134329083243424'>,)
kwargs = {'excluded_urls': '/health$,/ready$'}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'instrument_app' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_instrument_fastapi_success - AssertionError: Expected 'instrument_app' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 349 passed, 3 deselected, 1 warning in 2.48s
operator: core/AddNot, occurrence: 6, definition_name: create_span
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -164,7 +164,7 @@
             if span:
                 span.set_attribute("result", "success")
     """
-    if not settings.otel_enabled:
+    if not not settings.otel_enabled:
         yield None
         return
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..................................................F
=================================== FAILURES ===================================
_____________________ test_create_span_when_otel_disabled ______________________

    def test_create_span_when_otel_disabled():
        """Test create_span yields None when OTEL is disabled."""
        with patch("stampbot.telemetry.settings") as mock_settings:
            mock_settings.otel_enabled = False
            from stampbot.telemetry import create_span
    
            with create_span("test_span", {"attr": "value"}) as span:
>               assert span is None
E               assert NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False)) is None

tests/test_telemetry.py:99: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_when_otel_disabled - assert NonRecordingSpan(SpanContext(trace_id=0x00000000000000000000000000000000, span_id=0x0000000000000000, trace_flags=0x00, trace_state=[], is_remote=False)) is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 338 passed, 3 deselected, 1 warning in 2.37s
operator: core/AddNot, occurrence: 7, definition_name: create_span
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -172,7 +172,7 @@
 
     with tracer.start_as_current_span(name) as span:
         try:
-            if attributes:
+            if not attributes:
                 for key, value in attributes.items():
                     span.set_attribute(key, value)
             yield span
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
...............................................................F
=================================== FAILURES ===================================
_______________________ test_create_span_with_exception ________________________

    def test_create_span_with_exception():
        """Test create_span records exception when one is raised."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
        ):
            mock_settings.otel_enabled = True
    
            mock_span = Mock()
            mock_tracer = Mock()
            mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
            mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
            mock_get_tracer.return_value = mock_tracer
    
            from stampbot.telemetry import create_span
    
            test_error = ValueError("test error")
            try:
>               with create_span("test_span"):
                     ^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_telemetry.py:366: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/contextlib.py:141: in __enter__
    return next(self.gen)
           ^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

name = 'test_span', attributes = None, record_exception = True

    @contextmanager
    def create_span(
        name: str,
        attributes: dict[str, Any] | None = None,
        record_exception: bool = True,
    ) -> Iterator[Span | None]:
        """Create a span context manager for tracing operations.
    
        This is a convenience wrapper that handles span creation, attribute setting,
        and exception recording. If OpenTelemetry is disabled, yields None and
        the code block executes without tracing.
    
        Args:
            name: Name of the span
            attributes: Optional attributes to set on the span
            record_exception: Whether to record exceptions on the span
    
        Yields:
            The span if telemetry is enabled, None otherwise
    
        Example:
            with create_span("github.approve_pr", {"repo": "owner/repo", "pr": 123}) as span:
                # do work
                if span:
                    span.set_attribute("result", "success")
        """
        if not settings.otel_enabled:
            yield None
            return
    
        tracer = get_tracer("stampbot")
    
        with tracer.start_as_current_span(name) as span:
            try:
                if not attributes:
>                   for key, value in attributes.items():
                                      ^^^^^^^^^^^^^^^^
E                   AttributeError: 'NoneType' object has no attribute 'items'

stampbot/telemetry.py:176: AttributeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_with_exception - AttributeError: 'NoneType' object has no attribute 'items'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 351 passed, 3 deselected, 1 warning in 2.39s
operator: core/AddNot, occurrence: 8, definition_name: create_span
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -177,7 +177,7 @@
                     span.set_attribute(key, value)
             yield span
         except Exception as e:
-            if record_exception:
+            if not record_exception:
                 span.record_exception(e)
                 span.set_status(Status(StatusCode.ERROR, str(e)))
             raise
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
...............................................................F
=================================== FAILURES ===================================
_______________________ test_create_span_with_exception ________________________

    def test_create_span_with_exception():
        """Test create_span records exception when one is raised."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
        ):
            mock_settings.otel_enabled = True
    
            mock_span = Mock()
            mock_tracer = Mock()
            mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
            mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
            mock_get_tracer.return_value = mock_tracer
    
            from stampbot.telemetry import create_span
    
            test_error = ValueError("test error")
            try:
                with create_span("test_span"):
                    raise test_error
            except ValueError:
                pass
    
>           mock_span.record_exception.assert_called_once_with(test_error)

tests/test_telemetry.py:371: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='mock.record_exception' id='126493508276576'>
args = (ValueError('test error'),), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'record_exception' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_with_exception - AssertionError: Expected 'record_exception' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 351 passed, 3 deselected, 1 warning in 2.43s
operator: core/AddNot, occurrence: 9, definition_name: set_span_error
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -190,7 +190,7 @@
         span: The span to update (can be None if telemetry disabled)
         error: The exception that occurred
     """
-    if span:
+    if not span:
         span.record_exception(error)
         span.set_status(Status(StatusCode.ERROR, str(error)))
 
........................................................................ [ 15%]
..............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________

self = <stampbot.github_client.GitHubAppClient object at 0x722a4b2bec10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
>               pr.create_review(
                    body=comment,
                    event="APPROVE",
                )

stampbot/github_client.py:437: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().create_review' id='125525971564400'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestApprovePR object at 0x722a4e58ac10>

    def test_approve_pr_failure(self):
        """Test PR approval failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.create_review.side_effect = Exception("API Error")
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.approve_pr(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:668: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:466: in approve_pr
    set_span_error(span, e)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

span = None, error = Exception('API Error')

    def set_span_error(span: Span | None, error: Exception) -> None:
        """Set error status on a span.
    
        Args:
            span: The span to update (can be None if telemetry disabled)
            error: The exception that occurred
        """
        if not span:
>           span.record_exception(error)
            ^^^^^^^^^^^^^^^^^^^^^
E           AttributeError: 'NoneType' object has no attribute 'record_exception'

stampbot/telemetry.py:194: AttributeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - AttributeError: 'NoneType' object has no attribute 'record_exception'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected, 1 warning in 1.46s
operator: core/AddNot, occurrence: 10, definition_name: set_span_ok
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -201,7 +201,7 @@
     Args:
         span: The span to update (can be None if telemetry disabled)
     """
-    if span:
+    if not span:
         span.set_status(Status(StatusCode.OK))
 
 
........................................................................ [ 15%]
.............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________

self = <tests.test_github_client.TestApprovePR object at 0x7f1342daead0>

    def test_approve_pr_success(self):
        """Test successful PR approval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.approve_pr(123456, "owner/repo", 42)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:631: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:27:32.673137Z [error    ] Failed to approve PR #42 in owner/repo: 'NoneType' object has no attribute 'set_status' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "'NoneType' object has no attribute 'set_status'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:468 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "'NoneType' object has no attribute 'set_status'"}, 'event': "Failed to approve PR #42 in owner/repo: 'NoneType' object has no attribute 'set_status'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:27:32.673137Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 133 passed, 3 deselected, 1 warning in 1.42s
operator: core/AddNot, occurrence: 11, definition_name: add_span_attributes
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -212,7 +212,7 @@
         span: The span to update (can be None if telemetry disabled)
         attributes: Attributes to add
     """
-    if span:
+    if not span:
         for key, value in attributes.items():
             span.set_attribute(key, value)
 
........................................................................ [ 15%]
.............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________

self = <tests.test_github_client.TestApprovePR object at 0x711aa9b7aad0>

    def test_approve_pr_success(self):
        """Test successful PR approval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.approve_pr(123456, "owner/repo", 42)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:631: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:23:30.175125Z [error    ] Failed to approve PR #42 in owner/repo: 'NoneType' object has no attribute 'set_attribute' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "'NoneType' object has no attribute 'set_attribute'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:468 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "'NoneType' object has no attribute 'set_attribute'"}, 'event': "Failed to approve PR #42 in owner/repo: 'NoneType' object has no attribute 'set_attribute'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:23:30.175125Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 133 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceTrueWithFalse, occurrence: 0, definition_name: configure_telemetry
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -45,7 +45,7 @@
     # Log records gain trace and span IDs whenever tracing is enabled. This
     # runs after the provider attempt so the records also carry the service
     # name when an exporter exists.
-    LoggingInstrumentor().instrument(set_logging_format=True)
+    LoggingInstrumentor().instrument(set_logging_format=False)
 
     return provider
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
....................................................F
=================================== FAILURES ===================================
_____________________ test_configure_telemetry_no_endpoint _____________________

library_instrumentors = (<MagicMock name='RequestsInstrumentor' id='137895376227024'>, <MagicMock name='LoggingInstrumentor' id='137895376227360'>)

    def test_configure_telemetry_no_endpoint(library_instrumentors):
        """Test a missing endpoint exports nothing but still shapes log records."""
        requests_instrumentor, logging_instrumentor = library_instrumentors
    
        with patch("stampbot.telemetry.settings") as mock_settings:
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = None
            from stampbot.telemetry import configure_telemetry
    
            result = configure_telemetry()
            assert result is None
    
>       logging_instrumentor.return_value.instrument.assert_called_once_with(set_logging_format=True)

tests/test_telemetry.py:124: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='LoggingInstrumentor().instrument' id='137895376226352'>
args = (), kwargs = {'set_logging_format': True}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: instrument(set_logging_format=True)
E             Actual: instrument(set_logging_format=False)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "OpenTelemetry enabled but no endpoint configured", "logger": "stampbot.telemetry", "level": "warning", "timestamp": "2026-11-04T03:09:23.717688Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.telemetry:telemetry.py:60 {'event': 'OpenTelemetry enabled but no endpoint configured', 'logger': 'stampbot.telemetry', 'level': 'warning', 'timestamp': '2026-11-04T03:09:23.717688Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_no_endpoint - AssertionError: expected call not found.
Expected: instrument(set_logging_format=True)
  Actual: instrument(set_logging_format=False)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 340 passed, 3 deselected, 1 warning in 2.38s
operator: core/ReplaceTrueWithFalse, occurrence: 1, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -73,7 +73,7 @@
         provider = TracerProvider(resource=resource)
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
-        otel_insecure_requested = settings.get("otel_insecure", False) is True
+        otel_insecure_requested = settings.get("otel_insecure", False) is False
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
......................................................F
=================================== FAILURES ===================================
_______________________ test_configure_telemetry_success _______________________

    def test_configure_telemetry_success():
        """Test configure_telemetry returns provider when properly configured."""
        from stampbot.version import APP_VERSION
    
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource") as mock_resource,
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor") as mock_processor,
            patch("stampbot.telemetry.trace") as mock_trace,
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://localhost:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = False
    
            mock_provider = Mock()
            mock_provider_cls.return_value = mock_provider
    
            from stampbot.telemetry import configure_telemetry
    
            result = configure_telemetry()
    
            assert result == mock_provider
            mock_resource.create.assert_called_once_with(
                {"service.name": "test-service", "service.version": APP_VERSION}
            )
            mock_provider_cls.assert_called_once()
>           mock_exporter.assert_called_once_with(
                endpoint="http://localhost:4317",
                insecure=False,
            )

tests/test_telemetry.py:168: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='138070595745648'>, args = ()
kwargs = {'endpoint': 'http://localhost:4317', 'insecure': False}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
E             Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"otel_endpoint": "http://localhost:4317", "otel_transport": "plaintext"}, "event": "OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T04:01:26.362604Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://localhost:4317', 'otel_transport': 'plaintext'}, 'event': 'OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T04:01:26.362604Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_success - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
  Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 342 passed, 3 deselected, 1 warning in 2.37s
operator: core/ReplaceTrueWithFalse, occurrence: 2, definition_name: create_span
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -142,7 +142,7 @@
 def create_span(
     name: str,
     attributes: dict[str, Any] | None = None,
-    record_exception: bool = True,
+    record_exception: bool = False,
 ) -> Iterator[Span | None]:
     """Create a span context manager for tracing operations.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
...............................................................F
=================================== FAILURES ===================================
_______________________ test_create_span_with_exception ________________________

    def test_create_span_with_exception():
        """Test create_span records exception when one is raised."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
        ):
            mock_settings.otel_enabled = True
    
            mock_span = Mock()
            mock_tracer = Mock()
            mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
            mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
            mock_get_tracer.return_value = mock_tracer
    
            from stampbot.telemetry import create_span
    
            test_error = ValueError("test error")
            try:
                with create_span("test_span"):
                    raise test_error
            except ValueError:
                pass
    
>           mock_span.record_exception.assert_called_once_with(test_error)

tests/test_telemetry.py:371: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='mock.record_exception' id='138488529670496'>
args = (ValueError('test error'),), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'record_exception' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_with_exception - AssertionError: Expected 'record_exception' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 351 passed, 3 deselected, 1 warning in 2.38s
operator: core/ReplaceFalseWithTrue, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -73,7 +73,7 @@
         provider = TracerProvider(resource=resource)
 
         # TLS is the default. Plaintext export requires an explicit opt-in.
-        otel_insecure_requested = settings.get("otel_insecure", False) is True
+        otel_insecure_requested = settings.get("otel_insecure", True) is True
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceAndWithOr, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -75,7 +75,7 @@
         # TLS is the default. Plaintext export requires an explicit opt-in.
         otel_insecure_requested = settings.get("otel_insecure", False) is True
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
-        if otel_insecure_requested and endpoint_uses_https:
+        if otel_insecure_requested or endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
         otel_insecure = otel_insecure_requested and not endpoint_uses_https
         otlp_exporter = OTLPSpanExporter(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceAndWithOr, occurrence: 1, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -77,7 +77,7 @@
         endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
         if otel_insecure_requested and endpoint_uses_https:
             logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
-        otel_insecure = otel_insecure_requested and not endpoint_uses_https
+        otel_insecure = otel_insecure_requested or not endpoint_uses_https
         otlp_exporter = OTLPSpanExporter(
             endpoint=settings.otel_endpoint,
             insecure=otel_insecure,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
......................................................F
=================================== FAILURES ===================================
_______________________ test_configure_telemetry_success _______________________

    def test_configure_telemetry_success():
        """Test configure_telemetry returns provider when properly configured."""
        from stampbot.version import APP_VERSION
    
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource") as mock_resource,
            patch("stampbot.telemetry.TracerProvider") as mock_provider_cls,
            patch("stampbot.telemetry.OTLPSpanExporter") as mock_exporter,
            patch("stampbot.telemetry.BatchSpanProcessor") as mock_processor,
            patch("stampbot.telemetry.trace") as mock_trace,
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://localhost:4317"
            mock_settings.otel_service_name = "test-service"
            mock_settings.get.return_value = False
    
            mock_provider = Mock()
            mock_provider_cls.return_value = mock_provider
    
            from stampbot.telemetry import configure_telemetry
    
            result = configure_telemetry()
    
            assert result == mock_provider
            mock_resource.create.assert_called_once_with(
                {"service.name": "test-service", "service.version": APP_VERSION}
            )
            mock_provider_cls.assert_called_once()
>           mock_exporter.assert_called_once_with(
                endpoint="http://localhost:4317",
                insecure=False,
            )

tests/test_telemetry.py:168: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='OTLPSpanExporter' id='135017084467056'>, args = ()
kwargs = {'endpoint': 'http://localhost:4317', 'insecure': False}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
E             Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"otel_endpoint": "http://localhost:4317", "otel_transport": "plaintext"}, "event": "OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)", "logger": "stampbot.telemetry", "level": "info", "timestamp": "2026-11-04T03:54:31.403845Z", "_logger": "<_FixedFindCallerLogger stampbot.telemetry (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.telemetry:telemetry.py:98 {'extra': {'otel_endpoint': 'http://localhost:4317', 'otel_transport': 'plaintext'}, 'event': 'OpenTelemetry configured with endpoint: http://localhost:4317 (plaintext)', 'logger': 'stampbot.telemetry', 'level': 'info', 'timestamp': '2026-11-04T03:54:31.403845Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_success - AssertionError: expected call not found.
Expected: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=False)
  Actual: OTLPSpanExporter(endpoint='http://localhost:4317', insecure=True)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 342 passed, 3 deselected, 1 warning in 2.39s
operator: core/ExceptionReplacer, occurrence: 0, definition_name: _configure_exporter
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -107,7 +107,7 @@
 
         return provider
 
-    except Exception as e:
+    except CosmicRayTestingException as e:
         logger.error("Failed to configure OpenTelemetry: %s", e, extra={"error": str(e)})
         return None
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
............................................................F
=================================== FAILURES ===================================
______________________ test_configure_telemetry_exception ______________________

    def _configure_exporter() -> TracerProvider | None:
        """Create the tracer provider and OTLP exporter.
    
        Returns:
            TracerProvider when an endpoint is configured and setup succeeds, else None.
        """
        if not settings.otel_endpoint:
            logger.warning("OpenTelemetry enabled but no endpoint configured")
            return None
    
        try:
            # Create resource
>           resource = Resource.create(
                {
                    "service.name": settings.otel_service_name,
                    "service.version": APP_VERSION,
                }
            )

stampbot/telemetry.py:65: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='Resource.create' id='124439066668272'>
args = ({'service.name': <MagicMock name='settings.otel_service_name' id='124439066667936'>, 'service.version': '0.1.0'},)
kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: Connection failed

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

    def test_configure_telemetry_exception():
        """Test configure_telemetry handles exceptions gracefully."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.Resource") as mock_resource,
        ):
            mock_settings.otel_enabled = True
            mock_settings.otel_endpoint = "http://localhost:4317"
            mock_resource.create.side_effect = Exception("Connection failed")
    
            from stampbot.telemetry import configure_telemetry
    
>           result = configure_telemetry()
                     ^^^^^^^^^^^^^^^^^^^^^

tests/test_telemetry.py:309: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/telemetry.py:43: in configure_telemetry
    provider = _configure_exporter()
               ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def _configure_exporter() -> TracerProvider | None:
        """Create the tracer provider and OTLP exporter.
    
        Returns:
            TracerProvider when an endpoint is configured and setup succeeds, else None.
        """
        if not settings.otel_endpoint:
            logger.warning("OpenTelemetry enabled but no endpoint configured")
            return None
    
        try:
            # Create resource
            resource = Resource.create(
                {
                    "service.name": settings.otel_service_name,
                    "service.version": APP_VERSION,
                }
            )
    
            # Create tracer provider
            provider = TracerProvider(resource=resource)
    
            # TLS is the default. Plaintext export requires an explicit opt-in.
            otel_insecure_requested = settings.get("otel_insecure", False) is True
            endpoint_uses_https = urlparse(settings.otel_endpoint).scheme == "https"
            if otel_insecure_requested and endpoint_uses_https:
                logger.warning("Ignoring plaintext OTLP setting for an HTTPS endpoint")
            otel_insecure = otel_insecure_requested and not endpoint_uses_https
            otlp_exporter = OTLPSpanExporter(
                endpoint=settings.otel_endpoint,
                insecure=otel_insecure,
            )
    
            # Add span processor
            provider.add_span_processor(BatchSpanProcessor(otlp_exporter))
    
            # Set as global tracer provider
            trace.set_tracer_provider(provider)
    
            # PyGithub uses requests, so this gives every GitHub API call its own
            # child span with method, URL, and status code. Without it a
            # ``github.*`` span hides how many requests it made and which one was
            # slow.
            RequestsInstrumentor().instrument()
    
            logger.info(
                "OpenTelemetry configured with endpoint: %s (%s)",
                settings.otel_endpoint,
                "plaintext" if otel_insecure else "TLS",
                extra={
                    "otel_endpoint": settings.otel_endpoint,
                    "otel_transport": "plaintext" if otel_insecure else "TLS",
                },
            )
    
            return provider
    
>       except CosmicRayTestingException as e:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/telemetry.py:110: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_configure_telemetry_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 348 passed, 3 deselected, 1 warning in 2.39s
operator: core/ExceptionReplacer, occurrence: 1, definition_name: instrument_fastapi
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -122,7 +122,7 @@
         try:
             FastAPIInstrumentor.instrument_app(app, excluded_urls=TRACE_EXCLUDED_URLS)
             logger.info("FastAPI instrumented with OpenTelemetry")
-        except Exception as e:
+        except CosmicRayTestingException as e:
             logger.error("Failed to instrument FastAPI: %s", e, extra={"error": str(e)})
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..............................................................F
=================================== FAILURES ===================================
______________________ test_instrument_fastapi_exception _______________________

app = <Mock id='126428042497056'>

    def instrument_fastapi(app: Any) -> None:
        """Instrument FastAPI app with OpenTelemetry.
    
        Args:
            app: FastAPI application instance
        """
        if settings.otel_enabled:
            try:
>               FastAPIInstrumentor.instrument_app(app, excluded_urls=TRACE_EXCLUDED_URLS)

stampbot/telemetry.py:123: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='FastAPIInstrumentor.instrument_app' id='126428042496384'>
args = (<Mock id='126428042497056'>,)
kwargs = {'excluded_urls': '/health$,/ready$'}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: Instrumentation failed

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

    def test_instrument_fastapi_exception():
        """Test instrument_fastapi handles exceptions gracefully."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.FastAPIInstrumentor") as mock_instrumentor,
        ):
            mock_settings.otel_enabled = True
            mock_instrumentor.instrument_app.side_effect = Exception("Instrumentation failed")
    
            from stampbot.telemetry import instrument_fastapi
    
            mock_app = Mock()
            # Should not raise
>           instrument_fastapi(mock_app)

tests/test_telemetry.py:345: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <Mock id='126428042497056'>

    def instrument_fastapi(app: Any) -> None:
        """Instrument FastAPI app with OpenTelemetry.
    
        Args:
            app: FastAPI application instance
        """
        if settings.otel_enabled:
            try:
                FastAPIInstrumentor.instrument_app(app, excluded_urls=TRACE_EXCLUDED_URLS)
                logger.info("FastAPI instrumented with OpenTelemetry")
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/telemetry.py:125: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_instrument_fastapi_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 350 passed, 3 deselected, 1 warning in 2.42s
operator: core/ExceptionReplacer, occurrence: 2, definition_name: create_span
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -176,7 +176,7 @@
                 for key, value in attributes.items():
                     span.set_attribute(key, value)
             yield span
-        except Exception as e:
+        except CosmicRayTestingException as e:
             if record_exception:
                 span.record_exception(e)
                 span.set_status(Status(StatusCode.ERROR, str(e)))
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
...............................................................F
=================================== FAILURES ===================================
_______________________ test_create_span_with_exception ________________________

    def test_create_span_with_exception():
        """Test create_span records exception when one is raised."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
        ):
            mock_settings.otel_enabled = True
    
            mock_span = Mock()
            mock_tracer = Mock()
            mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
            mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
            mock_get_tracer.return_value = mock_tracer
    
            from stampbot.telemetry import create_span
    
            test_error = ValueError("test error")
            try:
                with create_span("test_span"):
>                   raise test_error
E                   ValueError: test error

tests/test_telemetry.py:367: ValueError

During handling of the above exception, another exception occurred:

    def test_create_span_with_exception():
        """Test create_span records exception when one is raised."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
        ):
            mock_settings.otel_enabled = True
    
            mock_span = Mock()
            mock_tracer = Mock()
            mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
            mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
            mock_get_tracer.return_value = mock_tracer
    
            from stampbot.telemetry import create_span
    
            test_error = ValueError("test error")
            try:
>               with create_span("test_span"):
                     ^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_telemetry.py:366: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/contextlib.py:162: in __exit__
    self.gen.throw(value)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

name = 'test_span', attributes = None, record_exception = True

    @contextmanager
    def create_span(
        name: str,
        attributes: dict[str, Any] | None = None,
        record_exception: bool = True,
    ) -> Iterator[Span | None]:
        """Create a span context manager for tracing operations.
    
        This is a convenience wrapper that handles span creation, attribute setting,
        and exception recording. If OpenTelemetry is disabled, yields None and
        the code block executes without tracing.
    
        Args:
            name: Name of the span
            attributes: Optional attributes to set on the span
            record_exception: Whether to record exceptions on the span
    
        Yields:
            The span if telemetry is enabled, None otherwise
    
        Example:
            with create_span("github.approve_pr", {"repo": "owner/repo", "pr": 123}) as span:
                # do work
                if span:
                    span.set_attribute("result", "success")
        """
        if not settings.otel_enabled:
            yield None
            return
    
        tracer = get_tracer("stampbot")
    
        with tracer.start_as_current_span(name) as span:
            try:
                if attributes:
                    for key, value in attributes.items():
                        span.set_attribute(key, value)
                yield span
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/telemetry.py:179: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_with_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 351 passed, 3 deselected, 1 warning in 2.39s
operator: core/RemoveDecorator, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -136,9 +136,6 @@
         Tracer instance (no-op if telemetry disabled)
     """
     return trace.get_tracer(name)
-
-
-@contextmanager
 def create_span(
     name: str,
     attributes: dict[str, Any] | None = None,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x76f4c039f240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
>       assert response.status_code == 200
E       assert 500 == 200
E        +  where 500 = <Response [500 Internal Server Error]>.status_code

tests/test_main.py:365: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"error": "'generator' object does not support the context manager protocol (missed __exit__ method)"}, "event": "Error handling webhook event: 'generator' object does not support the context manager protocol (missed __exit__ method)", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T03:30:21.623036Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:30:21.623556Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:548 {'extra': {'error': "'generator' object does not support the context manager protocol (missed __exit__ method)"}, 'event': "Error handling webhook event: 'generator' object does not support the context manager protocol (missed __exit__ method)", 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T03:30:21.623036Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
 +  where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.13s
operator: core/ZeroIterationForLoop, occurrence: 0, definition_name: create_span
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -173,7 +173,7 @@
     with tracer.start_as_current_span(name) as span:
         try:
             if attributes:
-                for key, value in attributes.items():
+                for key, value in []:
                     span.set_attribute(key, value)
             yield span
         except Exception as e:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
................................................................F
=================================== FAILURES ===================================
________________ test_create_span_with_attributes_when_enabled _________________

    def test_create_span_with_attributes_when_enabled():
        """Test create_span sets attributes on span when OTEL is enabled."""
        with (
            patch("stampbot.telemetry.settings") as mock_settings,
            patch("stampbot.telemetry.get_tracer") as mock_get_tracer,
        ):
            mock_settings.otel_enabled = True
    
            mock_span = Mock()
            mock_tracer = Mock()
            mock_tracer.start_as_current_span.return_value.__enter__ = Mock(return_value=mock_span)
            mock_tracer.start_as_current_span.return_value.__exit__ = Mock(return_value=False)
            mock_get_tracer.return_value = mock_tracer
    
            from stampbot.telemetry import create_span
    
            with create_span("test_span", {"key1": "value1", "key2": "value2"}) as span:
                assert span == mock_span
    
            # Verify attributes were set on the span
>           assert mock_span.set_attribute.call_count == 2
E           AssertionError: assert 0 == 2
E            +  where 0 = <Mock name='mock.set_attribute' id='128503324208032'>.call_count
E            +    where <Mock name='mock.set_attribute' id='128503324208032'> = <Mock id='128503324207024'>.set_attribute

tests/test_telemetry.py:395: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_create_span_with_attributes_when_enabled - AssertionError: assert 0 == 2
 +  where 0 = <Mock name='mock.set_attribute' id='128503324208032'>.call_count
 +    where <Mock name='mock.set_attribute' id='128503324208032'> = <Mock id='128503324207024'>.set_attribute
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 352 passed, 3 deselected, 1 warning in 2.40s
operator: core/ZeroIterationForLoop, occurrence: 1, definition_name: add_span_attributes
--- mutation diff ---
--- a/stampbot/telemetry.py
+++ b/stampbot/telemetry.py
@@ -213,6 +213,6 @@
         attributes: Attributes to add
     """
     if span:
-        for key, value in attributes.items():
+        for key, value in []:
             span.set_attribute(key, value)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.................................................F
=================================== FAILURES ===================================
______________________ test_add_span_attributes_with_span ______________________

    def test_add_span_attributes_with_span():
        """Test add_span_attributes adds attributes to span."""
        from stampbot.telemetry import add_span_attributes
    
        mock_span = Mock()
        attributes = {"key1": "value1", "key2": "value2"}
    
        add_span_attributes(mock_span, attributes)
    
>       assert mock_span.set_attribute.call_count == 2
E       AssertionError: assert 0 == 2
E        +  where 0 = <Mock name='mock.set_attribute' id='140267079965328'>.call_count
E        +    where <Mock name='mock.set_attribute' id='140267079965328'> = <Mock id='140267079963984'>.set_attribute

tests/test_telemetry.py:87: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_telemetry.py::test_add_span_attributes_with_span - AssertionError: assert 0 == 2
 +  where 0 = <Mock name='mock.set_attribute' id='140267079965328'>.call_count
 +    where <Mock name='mock.set_attribute' id='140267079965328'> = <Mock id='140267079963984'>.set_attribute
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 337 passed, 3 deselected, 1 warning in 2.37s
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -115,7 +115,7 @@
             return False
 
         expected_signature = (
-            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+            "sha256=" - hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
         )
 
         try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x730498d8e9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x730497f2af90>
payload = b'{"test": "data"}', signature = 'sha256=invalid'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
>           "sha256=" - hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
E       TypeError: unsupported operand type(s) for -: 'str' and 'str'

stampbot/webhook_handler.py:118: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:31:46.586656Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for -: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.24s
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 1, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if command in repo_config.approve_commands - repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75cdd9d61d90>
mock_github_client = <MagicMock name='github_client' id='129527019642160'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x75cdd9d61d90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
            command = command_match.group(1).lower()
            add_span_attributes(span, {"chatops.command": command})
    
            if command == "help":
                success = await self._post_help(
                    installation_id,
                    repo_full_name,
                    pr_number,
                    repo_config,
                )
    
                chatops_commands_total.labels(
                    command="help",
                    status="success" if success else "failure",
                ).inc()
    
                add_span_attributes(
                    span, {"chatops.result": "help_posted" if success else "help_failed"}
                )
                set_span_ok(span)
    
                return {
                    "status": "success" if success else "error",
                    "message": "Help message posted" if success else "Failed to post help message",
                }
    
>           if command in repo_config.approve_commands - repo_config.unapprove_commands:
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for -: 'list' and 'list'

stampbot/webhook_handler.py:730: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:37:14.279704Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:37:14.280118Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:37:14.279704Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:37:14.280118Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - TypeError: unsupported operand type(s) for -: 'list' and 'list'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 2, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -864,7 +864,7 @@
         if repo_config.required_labels:
             filters.append(
                 "required labels: "
-                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
+                - ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
         if repo_config.required_title_patterns:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a36e073ac80>
mock_github_client = <MagicMock name='github_client' id='134376136105264'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7a36e073ac80>
repo_config = <stampbot.config.RepoConfig object at 0x7a36e0749910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
>               "required labels: "
                - ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
E           TypeError: unsupported operand type(s) for -: 'str' and 'str'

stampbot/webhook_handler.py:866: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:27:11.350245Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:27:11.350245Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for -: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 3, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -869,7 +869,7 @@
         if repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
-                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
+                - ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
         if repo_config.allowed_users:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ab63863b070>
mock_github_client = <MagicMock name='github_client' id='134923072249136'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7ab63863b070>
repo_config = <stampbot.config.RepoConfig object at 0x7ab638649910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
>               "required title patterns: "
                - ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
E           TypeError: unsupported operand type(s) for -: 'str' and 'str'

stampbot/webhook_handler.py:871: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:08:50.329687Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:08:50.329687Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for -: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.88s
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 4, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -873,7 +873,7 @@
             )
         if repo_config.allowed_users:
             filters.append(
-                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
+                "allowed users: " - ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
         if repo_config.allowed_teams:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x766e74296c80>
mock_github_client = <MagicMock name='github_client' id='130216789203600'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x766e74296c80>
repo_config = <stampbot.config.RepoConfig object at 0x766e6fcc9910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
>               "allowed users: " - ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
E           TypeError: unsupported operand type(s) for -: 'str' and 'str'

stampbot/webhook_handler.py:876: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:11:10.722913Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:11:10.722913Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for -: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.88s
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 5, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -877,7 +877,7 @@
             )
         if repo_config.allowed_teams:
             filters.append(
-                "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
+                "allowed teams: " - ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
         if filters:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74c55bf6ac80>
mock_github_client = <MagicMock name='github_client' id='128391091641648'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x74c55bf6ac80>
repo_config = <stampbot.config.RepoConfig object at 0x74c55bb0d910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
        if repo_config.allowed_teams:
            filters.append(
>               "allowed teams: " - ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
            )
E           TypeError: unsupported operand type(s) for -: 'str' and 'str'

stampbot/webhook_handler.py:880: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:25:53.378019Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:25:53.378019Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for -: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.90s
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -115,7 +115,7 @@
             return False
 
         expected_signature = (
-            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+            "sha256=" * hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
         )
 
         try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7144502929c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x71444f42ef90>
payload = b'{"test": "data"}', signature = 'sha256=invalid'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
>           "sha256=" * hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
E       TypeError: can't multiply sequence by non-int of type 'str'

stampbot/webhook_handler.py:118: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:23:48.630031Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: can't multiply sequence by non-int of type 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.22s
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 1, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if command in repo_config.approve_commands * repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7695cbb65010>
mock_github_client = <MagicMock name='github_client' id='130385759427888'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7695cbb65010>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
            command = command_match.group(1).lower()
            add_span_attributes(span, {"chatops.command": command})
    
            if command == "help":
                success = await self._post_help(
                    installation_id,
                    repo_full_name,
                    pr_number,
                    repo_config,
                )
    
                chatops_commands_total.labels(
                    command="help",
                    status="success" if success else "failure",
                ).inc()
    
                add_span_attributes(
                    span, {"chatops.result": "help_posted" if success else "help_failed"}
                )
                set_span_ok(span)
    
                return {
                    "status": "success" if success else "error",
                    "message": "Help message posted" if success else "Failed to post help message",
                }
    
>           if command in repo_config.approve_commands * repo_config.unapprove_commands:
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: can't multiply sequence by non-int of type 'list'

stampbot/webhook_handler.py:730: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:00:12.568247Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:00:12.568668Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:00:12.568247Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:00:12.568668Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - TypeError: can't multiply sequence by non-int of type 'list'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.40s
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 2, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -864,7 +864,7 @@
         if repo_config.required_labels:
             filters.append(
                 "required labels: "
-                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
+                * ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
         if repo_config.required_title_patterns:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x79be64386c80>
mock_github_client = <MagicMock name='github_client' id='133858656443696'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x79be64386c80>
repo_config = <stampbot.config.RepoConfig object at 0x79be64271910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
>               "required labels: "
                * ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
E           TypeError: can't multiply sequence by non-int of type 'str'

stampbot/webhook_handler.py:866: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:42:52.284086Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:42:52.284086Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: can't multiply sequence by non-int of type 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.91s
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 3, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -869,7 +869,7 @@
         if repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
-                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
+                * ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
         if repo_config.allowed_users:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7edc8220b2a0>
mock_github_client = <MagicMock name='github_client' id='139485563755824'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7edc8220b2a0>
repo_config = <stampbot.config.RepoConfig object at 0x7edc820e5910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
>               "required title patterns: "
                * ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
E           TypeError: can't multiply sequence by non-int of type 'str'

stampbot/webhook_handler.py:871: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:48:30.681569Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:48:30.681569Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: can't multiply sequence by non-int of type 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.96s
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 4, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -873,7 +873,7 @@
             )
         if repo_config.allowed_users:
             filters.append(
-                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
+                "allowed users: " * ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
         if repo_config.allowed_teams:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74e31f7040c0>
mock_github_client = <MagicMock name='github_client' id='128518856531248'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x74e31f7040c0>
repo_config = <stampbot.config.RepoConfig object at 0x74e31f24d910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
>               "allowed users: " * ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
E           TypeError: can't multiply sequence by non-int of type 'str'

stampbot/webhook_handler.py:876: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:30:29.041951Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:30:29.041951Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: can't multiply sequence by non-int of type 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.91s
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 5, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -877,7 +877,7 @@
             )
         if repo_config.allowed_teams:
             filters.append(
-                "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
+                "allowed teams: " * ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
         if filters:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x772082862c80>
mock_github_client = <MagicMock name='github_client' id='130981542190384'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x772082862c80>
repo_config = <stampbot.config.RepoConfig object at 0x77208427d910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
        if repo_config.allowed_teams:
            filters.append(
>               "allowed teams: " * ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
            )
E           TypeError: can't multiply sequence by non-int of type 'str'

stampbot/webhook_handler.py:880: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:36:15.105565Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:36:15.105565Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: can't multiply sequence by non-int of type 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.88s
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -115,7 +115,7 @@
             return False
 
         expected_signature = (
-            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+            "sha256=" / hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
         )
 
         try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x758830e729c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x758830032f90>
payload = b'{"test": "data"}', signature = 'sha256=invalid'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
>           "sha256=" / hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
E       TypeError: unsupported operand type(s) for /: 'str' and 'str'

stampbot/webhook_handler.py:118: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:51:24.986625Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for /: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 1, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if command in repo_config.approve_commands / repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x702700a84f80>
mock_github_client = <MagicMock name='github_client' id='123312835566896'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x702700a84f80>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
            command = command_match.group(1).lower()
            add_span_attributes(span, {"chatops.command": command})
    
            if command == "help":
                success = await self._post_help(
                    installation_id,
                    repo_full_name,
                    pr_number,
                    repo_config,
                )
    
                chatops_commands_total.labels(
                    command="help",
                    status="success" if success else "failure",
                ).inc()
    
                add_span_attributes(
                    span, {"chatops.result": "help_posted" if success else "help_failed"}
                )
                set_span_ok(span)
    
                return {
                    "status": "success" if success else "error",
                    "message": "Help message posted" if success else "Failed to post help message",
                }
    
>           if command in repo_config.approve_commands / repo_config.unapprove_commands:
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for /: 'list' and 'list'

stampbot/webhook_handler.py:730: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:36:02.956203Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:36:02.956630Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:36:02.956203Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:36:02.956630Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - TypeError: unsupported operand type(s) for /: 'list' and 'list'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 2, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -864,7 +864,7 @@
         if repo_config.required_labels:
             filters.append(
                 "required labels: "
-                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
+                / ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
         if repo_config.required_title_patterns:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fbe48362d60>
mock_github_client = <MagicMock name='github_client' id='140455255334192'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7fbe48362d60>
repo_config = <stampbot.config.RepoConfig object at 0x7fbe48359910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
>               "required labels: "
                / ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
E           TypeError: unsupported operand type(s) for /: 'str' and 'str'

stampbot/webhook_handler.py:866: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:51:29.143700Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:51:29.143700Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for /: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.92s
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 3, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -869,7 +869,7 @@
         if repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
-                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
+                / ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
         if repo_config.allowed_users:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ef58fa67070>
mock_github_client = <MagicMock name='github_client' id='139593165462832'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7ef58fa67070>
repo_config = <stampbot.config.RepoConfig object at 0x7ef58fa31910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
>               "required title patterns: "
                / ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
E           TypeError: unsupported operand type(s) for /: 'str' and 'str'

stampbot/webhook_handler.py:871: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:37:41.029273Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:37:41.029273Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for /: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.90s
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 4, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -873,7 +873,7 @@
             )
         if repo_config.allowed_users:
             filters.append(
-                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
+                "allowed users: " / ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
         if repo_config.allowed_teams:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x706afe32ec80>
mock_github_client = <MagicMock name='github_client' id='123604832034096'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x706afe32ec80>
repo_config = <stampbot.config.RepoConfig object at 0x706afe37d910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
>               "allowed users: " / ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
E           TypeError: unsupported operand type(s) for /: 'str' and 'str'

stampbot/webhook_handler.py:876: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:12:04.948296Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:12:04.948296Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for /: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.87s
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 5, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -877,7 +877,7 @@
             )
         if repo_config.allowed_teams:
             filters.append(
-                "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
+                "allowed teams: " / ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
         if filters:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7e223d532c80>
mock_github_client = <MagicMock name='github_client' id='138685520977200'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7e223d532c80>
repo_config = <stampbot.config.RepoConfig object at 0x7e223d591910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
        if repo_config.allowed_teams:
            filters.append(
>               "allowed teams: " / ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
            )
E           TypeError: unsupported operand type(s) for /: 'str' and 'str'

stampbot/webhook_handler.py:880: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:49:14.434584Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:49:14.434584Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for /: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.95s
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -115,7 +115,7 @@
             return False
 
         expected_signature = (
-            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+            "sha256=" // hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
         )
 
         try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x77436df8e9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x77436d13af90>
payload = b'{"test": "data"}', signature = 'sha256=invalid'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
>           "sha256=" // hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
E       TypeError: unsupported operand type(s) for //: 'str' and 'str'

stampbot/webhook_handler.py:118: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:05:15.398037Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for //: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 1, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if command in repo_config.approve_commands // repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7b8d30a61d90>
mock_github_client = <MagicMock name='github_client' id='135846356311344'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7b8d30a61d90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
            command = command_match.group(1).lower()
            add_span_attributes(span, {"chatops.command": command})
    
            if command == "help":
                success = await self._post_help(
                    installation_id,
                    repo_full_name,
                    pr_number,
                    repo_config,
                )
    
                chatops_commands_total.labels(
                    command="help",
                    status="success" if success else "failure",
                ).inc()
    
                add_span_attributes(
                    span, {"chatops.result": "help_posted" if success else "help_failed"}
                )
                set_span_ok(span)
    
                return {
                    "status": "success" if success else "error",
                    "message": "Help message posted" if success else "Failed to post help message",
                }
    
>           if command in repo_config.approve_commands // repo_config.unapprove_commands:
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for //: 'list' and 'list'

stampbot/webhook_handler.py:730: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:08:46.064643Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:08:46.065044Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:08:46.064643Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:08:46.065044Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - TypeError: unsupported operand type(s) for //: 'list' and 'list'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 2, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -864,7 +864,7 @@
         if repo_config.required_labels:
             filters.append(
                 "required labels: "
-                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
+                // ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
         if repo_config.required_title_patterns:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x72717256ac80>
mock_github_client = <MagicMock name='github_client' id='125831604739376'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x72717256ac80>
repo_config = <stampbot.config.RepoConfig object at 0x727172799910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
>               "required labels: "
                // ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
E           TypeError: unsupported operand type(s) for //: 'str' and 'str'

stampbot/webhook_handler.py:866: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:33:42.626214Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:33:42.626214Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for //: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 3, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -869,7 +869,7 @@
         if repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
-                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
+                // ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
         if repo_config.allowed_users:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x76acd613ec80>
mock_github_client = <MagicMock name='github_client' id='130484720557360'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x76acd613ec80>
repo_config = <stampbot.config.RepoConfig object at 0x76acd5ce1910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
>               "required title patterns: "
                // ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
E           TypeError: unsupported operand type(s) for //: 'str' and 'str'

stampbot/webhook_handler.py:871: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:36:01.435799Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:36:01.435799Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for //: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.86s
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 4, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -873,7 +873,7 @@
             )
         if repo_config.allowed_users:
             filters.append(
-                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
+                "allowed users: " // ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
         if repo_config.allowed_teams:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x78274ad0ac80>
mock_github_client = <MagicMock name='github_client' id='132110177983792'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x78274ad0ac80>
repo_config = <stampbot.config.RepoConfig object at 0x78274ad41910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
>               "allowed users: " // ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
E           TypeError: unsupported operand type(s) for //: 'str' and 'str'

stampbot/webhook_handler.py:876: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:40:45.093472Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:40:45.093472Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for //: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.90s
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 5, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -877,7 +877,7 @@
             )
         if repo_config.allowed_teams:
             filters.append(
-                "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
+                "allowed teams: " // ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
         if filters:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a2bce086c80>
mock_github_client = <MagicMock name='github_client' id='134328588377392'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7a2bce086c80>
repo_config = <stampbot.config.RepoConfig object at 0x7a2bcfa79910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
        if repo_config.allowed_teams:
            filters.append(
>               "allowed teams: " // ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
            )
E           TypeError: unsupported operand type(s) for //: 'str' and 'str'

stampbot/webhook_handler.py:880: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:23:42.696700Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:23:42.696700Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for //: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -115,7 +115,7 @@
             return False
 
         expected_signature = (
-            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+            "sha256=" % hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
         )
 
         try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7f020986a9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7f0208a2af90>
payload = b'{"test": "data"}', signature = 'sha256=invalid'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
>           "sha256=" % hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
E       TypeError: not all arguments converted during string formatting

stampbot/webhook_handler.py:118: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:16:44.313832Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: not all arguments converted during string formatting
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.22s
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 1, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if command in repo_config.approve_commands % repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f7f6cb79d90>
mock_github_client = <MagicMock name='github_client' id='140185280093488'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7f7f6cb79d90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
            command = command_match.group(1).lower()
            add_span_attributes(span, {"chatops.command": command})
    
            if command == "help":
                success = await self._post_help(
                    installation_id,
                    repo_full_name,
                    pr_number,
                    repo_config,
                )
    
                chatops_commands_total.labels(
                    command="help",
                    status="success" if success else "failure",
                ).inc()
    
                add_span_attributes(
                    span, {"chatops.result": "help_posted" if success else "help_failed"}
                )
                set_span_ok(span)
    
                return {
                    "status": "success" if success else "error",
                    "message": "Help message posted" if success else "Failed to post help message",
                }
    
>           if command in repo_config.approve_commands % repo_config.unapprove_commands:
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for %: 'list' and 'list'

stampbot/webhook_handler.py:730: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:40:48.691030Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:40:48.691488Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:40:48.691030Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:40:48.691488Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - TypeError: unsupported operand type(s) for %: 'list' and 'list'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 2, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -864,7 +864,7 @@
         if repo_config.required_labels:
             filters.append(
                 "required labels: "
-                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
+                % ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
         if repo_config.required_title_patterns:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x71207453ac80>
mock_github_client = <MagicMock name='github_client' id='124384228121904'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x71207453ac80>
repo_config = <stampbot.config.RepoConfig object at 0x712074551910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
>               "required labels: "
                % ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
E           TypeError: not all arguments converted during string formatting

stampbot/webhook_handler.py:866: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:39:59.001008Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:39:59.001008Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: not all arguments converted during string formatting
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 3, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -869,7 +869,7 @@
         if repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
-                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
+                % ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
         if repo_config.allowed_users:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x71090f6fac80>
mock_github_client = <MagicMock name='github_client' id='124283750423856'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x71090f6fac80>
repo_config = <stampbot.config.RepoConfig object at 0x71090f649910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
>               "required title patterns: "
                % ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
E           TypeError: not all arguments converted during string formatting

stampbot/webhook_handler.py:871: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:15:29.230311Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:15:29.230311Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: not all arguments converted during string formatting
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.90s
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 4, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -873,7 +873,7 @@
             )
         if repo_config.allowed_users:
             filters.append(
-                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
+                "allowed users: " % ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
         if repo_config.allowed_teams:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x78d91d03ac80>
mock_github_client = <MagicMock name='github_client' id='132873913556272'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x78d91d03ac80>
repo_config = <stampbot.config.RepoConfig object at 0x78d91d04d910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
>               "allowed users: " % ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
E           TypeError: not all arguments converted during string formatting

stampbot/webhook_handler.py:876: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:19:47.368266Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:19:47.368266Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: not all arguments converted during string formatting
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.90s
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 5, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -877,7 +877,7 @@
             )
         if repo_config.allowed_teams:
             filters.append(
-                "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
+                "allowed teams: " % ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
         if filters:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73d57495b000>
mock_github_client = <MagicMock name='github_client' id='127360644685104'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x73d57495b000>
repo_config = <stampbot.config.RepoConfig object at 0x73d574945910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
        if repo_config.allowed_teams:
            filters.append(
>               "allowed teams: " % ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
            )
E           TypeError: not all arguments converted during string formatting

stampbot/webhook_handler.py:880: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:31:22.933857Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:31:22.933857Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: not all arguments converted during string formatting
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.91s
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -115,7 +115,7 @@
             return False
 
         expected_signature = (
-            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+            "sha256=" ** hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
         )
 
         try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x744cf577a9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x744cf492ef90>
payload = b'{"test": "data"}', signature = 'sha256=invalid'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
>           "sha256=" ** hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
E       TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'

stampbot/webhook_handler.py:118: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:15:35.454351Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.22s
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 1, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if command in repo_config.approve_commands ** repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7e9617969010>
mock_github_client = <MagicMock name='github_client' id='139183123389744'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7e9617969010>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
            command = command_match.group(1).lower()
            add_span_attributes(span, {"chatops.command": command})
    
            if command == "help":
                success = await self._post_help(
                    installation_id,
                    repo_full_name,
                    pr_number,
                    repo_config,
                )
    
                chatops_commands_total.labels(
                    command="help",
                    status="success" if success else "failure",
                ).inc()
    
                add_span_attributes(
                    span, {"chatops.result": "help_posted" if success else "help_failed"}
                )
                set_span_ok(span)
    
                return {
                    "status": "success" if success else "error",
                    "message": "Help message posted" if success else "Failed to post help message",
                }
    
>           if command in repo_config.approve_commands ** repo_config.unapprove_commands:
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'list'

stampbot/webhook_handler.py:730: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:29:16.838798Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:29:16.839242Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:29:16.838798Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:29:16.839242Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - TypeError: unsupported operand type(s) for ** or pow(): 'list' and 'list'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.45s
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 2, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -864,7 +864,7 @@
         if repo_config.required_labels:
             filters.append(
                 "required labels: "
-                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
+                ** ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
         if repo_config.required_title_patterns:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7769dfec80c0>
mock_github_client = <MagicMock name='github_client' id='131296704142640'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7769dfec80c0>
repo_config = <stampbot.config.RepoConfig object at 0x7769dfef9910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
>               "required labels: "
                ** ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
E           TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'

stampbot/webhook_handler.py:866: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:21:27.230338Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:21:27.230338Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.90s
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 3, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -869,7 +869,7 @@
         if repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
-                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
+                ** ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
         if repo_config.allowed_users:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d6cbbce2c80>
mock_github_client = <MagicMock name='github_client' id='137905982512432'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7d6cbbce2c80>
repo_config = <stampbot.config.RepoConfig object at 0x7d6cbb9d5910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
>               "required title patterns: "
                ** ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
E           TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'

stampbot/webhook_handler.py:871: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:17:04.812204Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:17:04.812204Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.91s
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 4, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -873,7 +873,7 @@
             )
         if repo_config.allowed_users:
             filters.append(
-                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
+                "allowed users: " ** ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
         if repo_config.allowed_teams:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77e66ec880c0>
mock_github_client = <MagicMock name='github_client' id='131831614348592'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x77e66ec880c0>
repo_config = <stampbot.config.RepoConfig object at 0x77e66ecb9910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
>               "allowed users: " ** ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
E           TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'

stampbot/webhook_handler.py:876: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:13:44.624830Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:13:44.624830Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.87s
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 5, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -877,7 +877,7 @@
             )
         if repo_config.allowed_teams:
             filters.append(
-                "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
+                "allowed teams: " ** ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
         if filters:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x743e697f2c80>
mock_github_client = <MagicMock name='github_client' id='127811446299952'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x743e697f2c80>
repo_config = <stampbot.config.RepoConfig object at 0x743e6973d850>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
        if repo_config.allowed_teams:
            filters.append(
>               "allowed teams: " ** ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
            )
E           TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'

stampbot/webhook_handler.py:880: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:56:33.236539Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:56:33.236539Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.85s
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -115,7 +115,7 @@
             return False
 
         expected_signature = (
-            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+            "sha256=" >> hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
         )
 
         try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x72a7efe829c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x72a7ef02ef90>
payload = b'{"test": "data"}', signature = 'sha256=invalid'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
>           "sha256=" >> hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
E       TypeError: unsupported operand type(s) for >>: 'str' and 'str'

stampbot/webhook_handler.py:118: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:34:44.562740Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for >>: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.21s
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 1, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if command in repo_config.approve_commands >> repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x705bff165d90>
mock_github_client = <MagicMock name='github_client' id='123540443389232'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x705bff165d90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
            command = command_match.group(1).lower()
            add_span_attributes(span, {"chatops.command": command})
    
            if command == "help":
                success = await self._post_help(
                    installation_id,
                    repo_full_name,
                    pr_number,
                    repo_config,
                )
    
                chatops_commands_total.labels(
                    command="help",
                    status="success" if success else "failure",
                ).inc()
    
                add_span_attributes(
                    span, {"chatops.result": "help_posted" if success else "help_failed"}
                )
                set_span_ok(span)
    
                return {
                    "status": "success" if success else "error",
                    "message": "Help message posted" if success else "Failed to post help message",
                }
    
>           if command in repo_config.approve_commands >> repo_config.unapprove_commands:
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for >>: 'list' and 'list'

stampbot/webhook_handler.py:730: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:27:17.923524Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:27:17.923949Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:27:17.923524Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:27:17.923949Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - TypeError: unsupported operand type(s) for >>: 'list' and 'list'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.45s
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 2, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -864,7 +864,7 @@
         if repo_config.required_labels:
             filters.append(
                 "required labels: "
-                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
+                >> ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
         if repo_config.required_title_patterns:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x720533b66c80>
mock_github_client = <MagicMock name='github_client' id='125366759415088'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x720533b66c80>
repo_config = <stampbot.config.RepoConfig object at 0x720533b35910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
>               "required labels: "
                >> ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
E           TypeError: unsupported operand type(s) for >>: 'str' and 'str'

stampbot/webhook_handler.py:866: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:52:27.511338Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:52:27.511338Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for >>: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.90s
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 3, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -869,7 +869,7 @@
         if repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
-                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
+                >> ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
         if repo_config.allowed_users:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73a661f6ec80>
mock_github_client = <MagicMock name='github_client' id='127158468730160'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x73a661f6ec80>
repo_config = <stampbot.config.RepoConfig object at 0x73a661f4d910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
>               "required title patterns: "
                >> ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
E           TypeError: unsupported operand type(s) for >>: 'str' and 'str'

stampbot/webhook_handler.py:871: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:23:38.342688Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:23:38.342688Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for >>: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 4, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -873,7 +873,7 @@
             )
         if repo_config.allowed_users:
             filters.append(
-                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
+                "allowed users: " >> ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
         if repo_config.allowed_teams:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x78cb64b0ec80>
mock_github_client = <MagicMock name='github_client' id='132814986730800'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x78cb64b0ec80>
repo_config = <stampbot.config.RepoConfig object at 0x78cb64b49910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
>               "allowed users: " >> ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
E           TypeError: unsupported operand type(s) for >>: 'str' and 'str'

stampbot/webhook_handler.py:876: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:01:51.988543Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:01:51.988543Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for >>: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.85s
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 5, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -877,7 +877,7 @@
             )
         if repo_config.allowed_teams:
             filters.append(
-                "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
+                "allowed teams: " >> ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
         if filters:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x786e105fb070>
mock_github_client = <MagicMock name='github_client' id='132414139161904'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x786e105fb070>
repo_config = <stampbot.config.RepoConfig object at 0x786e10551910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
        if repo_config.allowed_teams:
            filters.append(
>               "allowed teams: " >> ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
            )
E           TypeError: unsupported operand type(s) for >>: 'str' and 'str'

stampbot/webhook_handler.py:880: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:43:46.154442Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:43:46.154442Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for >>: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -115,7 +115,7 @@
             return False
 
         expected_signature = (
-            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+            "sha256=" << hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
         )
 
         try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x76ae172769c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x76ae1642af90>
payload = b'{"test": "data"}', signature = 'sha256=invalid'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
>           "sha256=" << hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
E       TypeError: unsupported operand type(s) for <<: 'str' and 'str'

stampbot/webhook_handler.py:118: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:41:45.786241Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for <<: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 1, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if command in repo_config.approve_commands << repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x746039f61d90>
mock_github_client = <MagicMock name='github_client' id='127956657560880'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x746039f61d90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
            command = command_match.group(1).lower()
            add_span_attributes(span, {"chatops.command": command})
    
            if command == "help":
                success = await self._post_help(
                    installation_id,
                    repo_full_name,
                    pr_number,
                    repo_config,
                )
    
                chatops_commands_total.labels(
                    command="help",
                    status="success" if success else "failure",
                ).inc()
    
                add_span_attributes(
                    span, {"chatops.result": "help_posted" if success else "help_failed"}
                )
                set_span_ok(span)
    
                return {
                    "status": "success" if success else "error",
                    "message": "Help message posted" if success else "Failed to post help message",
                }
    
>           if command in repo_config.approve_commands << repo_config.unapprove_commands:
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for <<: 'list' and 'list'

stampbot/webhook_handler.py:730: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:48:40.800311Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:48:40.800767Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:48:40.800311Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:48:40.800767Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - TypeError: unsupported operand type(s) for <<: 'list' and 'list'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.49s
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 2, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -864,7 +864,7 @@
         if repo_config.required_labels:
             filters.append(
                 "required labels: "
-                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
+                << ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
         if repo_config.required_title_patterns:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a7c6575c050>
mock_github_client = <MagicMock name='github_client' id='134674720103056'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7a7c6575c050>
repo_config = <stampbot.config.RepoConfig object at 0x7a7c65729910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
>               "required labels: "
                << ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
E           TypeError: unsupported operand type(s) for <<: 'str' and 'str'

stampbot/webhook_handler.py:866: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:13:15.884809Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:13:15.884809Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for <<: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.86s
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 3, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -869,7 +869,7 @@
         if repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
-                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
+                << ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
         if repo_config.allowed_users:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x79a7546d6c80>
mock_github_client = <MagicMock name='github_client' id='133759605939504'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x79a7546d6c80>
repo_config = <stampbot.config.RepoConfig object at 0x79a754645910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
>               "required title patterns: "
                << ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
E           TypeError: unsupported operand type(s) for <<: 'str' and 'str'

stampbot/webhook_handler.py:871: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:35:42.581505Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:35:42.581505Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for <<: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.93s
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 4, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -873,7 +873,7 @@
             )
         if repo_config.allowed_users:
             filters.append(
-                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
+                "allowed users: " << ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
         if repo_config.allowed_teams:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x780ea029ec80>
mock_github_client = <MagicMock name='github_client' id='132004235107632'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x780ea029ec80>
repo_config = <stampbot.config.RepoConfig object at 0x780ea02d5910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
>               "allowed users: " << ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
E           TypeError: unsupported operand type(s) for <<: 'str' and 'str'

stampbot/webhook_handler.py:876: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:31:07.012084Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:31:07.012084Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for <<: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 5, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -877,7 +877,7 @@
             )
         if repo_config.allowed_teams:
             filters.append(
-                "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
+                "allowed teams: " << ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
         if filters:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x70d6c8746c80>
mock_github_client = <MagicMock name='github_client' id='124067811876144'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x70d6c8746c80>
repo_config = <stampbot.config.RepoConfig object at 0x70d6c873d910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
        if repo_config.allowed_teams:
            filters.append(
>               "allowed teams: " << ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
            )
E           TypeError: unsupported operand type(s) for <<: 'str' and 'str'

stampbot/webhook_handler.py:880: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:19:39.380129Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:19:39.380129Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for <<: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -115,7 +115,7 @@
             return False
 
         expected_signature = (
-            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+            "sha256=" | hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
         )
 
         try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x71b7ae69a9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x71b7ad73af90>
payload = b'{"test": "data"}', signature = 'sha256=invalid'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
>           "sha256=" | hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
E       TypeError: unsupported operand type(s) for |: 'str' and 'str'

stampbot/webhook_handler.py:118: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:39:00.455398Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for |: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 1, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if command in repo_config.approve_commands | repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77bc105f8f80>
mock_github_client = <MagicMock name='github_client' id='131649629969712'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x77bc105f8f80>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
            command = command_match.group(1).lower()
            add_span_attributes(span, {"chatops.command": command})
    
            if command == "help":
                success = await self._post_help(
                    installation_id,
                    repo_full_name,
                    pr_number,
                    repo_config,
                )
    
                chatops_commands_total.labels(
                    command="help",
                    status="success" if success else "failure",
                ).inc()
    
                add_span_attributes(
                    span, {"chatops.result": "help_posted" if success else "help_failed"}
                )
                set_span_ok(span)
    
                return {
                    "status": "success" if success else "error",
                    "message": "Help message posted" if success else "Failed to post help message",
                }
    
>           if command in repo_config.approve_commands | repo_config.unapprove_commands:
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for |: 'list' and 'list'

stampbot/webhook_handler.py:730: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:19:18.745053Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:19:18.745492Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:19:18.745053Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:19:18.745492Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - TypeError: unsupported operand type(s) for |: 'list' and 'list'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 2, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -864,7 +864,7 @@
         if repo_config.required_labels:
             filters.append(
                 "required labels: "
-                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
+                | ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
         if repo_config.required_title_patterns:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x751c5fee40c0>
mock_github_client = <MagicMock name='github_client' id='128764820020528'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x751c5fee40c0>
repo_config = <stampbot.config.RepoConfig object at 0x751c5fc9d910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
>               "required labels: "
                | ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
E           TypeError: unsupported operand type(s) for |: 'str' and 'str'

stampbot/webhook_handler.py:866: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:19:28.887123Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:19:28.887123Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for |: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.92s
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 3, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -869,7 +869,7 @@
         if repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
-                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
+                | ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
         if repo_config.allowed_users:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x737027fcc0c0>
mock_github_client = <MagicMock name='github_client' id='126925636443440'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x737027fcc0c0>
repo_config = <stampbot.config.RepoConfig object at 0x737027ee1910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
>               "required title patterns: "
                | ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
E           TypeError: unsupported operand type(s) for |: 'str' and 'str'

stampbot/webhook_handler.py:871: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:01:00.123974Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:01:00.123974Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for |: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.87s
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 4, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -873,7 +873,7 @@
             )
         if repo_config.allowed_users:
             filters.append(
-                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
+                "allowed users: " | ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
         if repo_config.allowed_teams:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7688cfe56c80>
mock_github_client = <MagicMock name='github_client' id='130330066935088'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7688cfe56c80>
repo_config = <stampbot.config.RepoConfig object at 0x7688cfab9910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
>               "allowed users: " | ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
E           TypeError: unsupported operand type(s) for |: 'str' and 'str'

stampbot/webhook_handler.py:876: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:59:31.245511Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:59:31.245511Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for |: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.85s
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 5, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -877,7 +877,7 @@
             )
         if repo_config.allowed_teams:
             filters.append(
-                "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
+                "allowed teams: " | ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
         if filters:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7b1eebfeac80>
mock_github_client = <MagicMock name='github_client' id='135372829515056'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7b1eebfeac80>
repo_config = <stampbot.config.RepoConfig object at 0x7b1eebda5910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
        if repo_config.allowed_teams:
            filters.append(
>               "allowed teams: " | ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
            )
E           TypeError: unsupported operand type(s) for |: 'str' and 'str'

stampbot/webhook_handler.py:880: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:53:04.264320Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:53:04.264320Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for |: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -115,7 +115,7 @@
             return False
 
         expected_signature = (
-            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+            "sha256=" & hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
         )
 
         try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7250daa8a9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7250d9c0ef90>
payload = b'{"test": "data"}', signature = 'sha256=invalid'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
>           "sha256=" & hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
E       TypeError: unsupported operand type(s) for &: 'str' and 'str'

stampbot/webhook_handler.py:118: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:09:08.930598Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for &: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 1, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if command in repo_config.approve_commands & repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7e3f5e16dd90>
mock_github_client = <MagicMock name='github_client' id='138810643995952'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7e3f5e16dd90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
            command = command_match.group(1).lower()
            add_span_attributes(span, {"chatops.command": command})
    
            if command == "help":
                success = await self._post_help(
                    installation_id,
                    repo_full_name,
                    pr_number,
                    repo_config,
                )
    
                chatops_commands_total.labels(
                    command="help",
                    status="success" if success else "failure",
                ).inc()
    
                add_span_attributes(
                    span, {"chatops.result": "help_posted" if success else "help_failed"}
                )
                set_span_ok(span)
    
                return {
                    "status": "success" if success else "error",
                    "message": "Help message posted" if success else "Failed to post help message",
                }
    
>           if command in repo_config.approve_commands & repo_config.unapprove_commands:
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for &: 'list' and 'list'

stampbot/webhook_handler.py:730: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:47.988251Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:18:47.988681Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:47.988251Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:18:47.988681Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - TypeError: unsupported operand type(s) for &: 'list' and 'list'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.45s
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 2, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -864,7 +864,7 @@
         if repo_config.required_labels:
             filters.append(
                 "required labels: "
-                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
+                & ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
         if repo_config.required_title_patterns:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x778d761d70e0>
mock_github_client = <MagicMock name='github_client' id='131449495526704'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x778d761d70e0>
repo_config = <stampbot.config.RepoConfig object at 0x778d78591850>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
>               "required labels: "
                & ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
E           TypeError: unsupported operand type(s) for &: 'str' and 'str'

stampbot/webhook_handler.py:866: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:25:10.883707Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:25:10.883707Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for &: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.87s
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 3, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -869,7 +869,7 @@
         if repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
-                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
+                & ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
         if repo_config.allowed_users:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75d69bac6c80>
mock_github_client = <MagicMock name='github_client' id='129564687244592'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x75d69bac6c80>
repo_config = <stampbot.config.RepoConfig object at 0x75d69baf5910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
>               "required title patterns: "
                & ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
E           TypeError: unsupported operand type(s) for &: 'str' and 'str'

stampbot/webhook_handler.py:871: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:07.298940Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:07.298940Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for &: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.91s
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 4, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -873,7 +873,7 @@
             )
         if repo_config.allowed_users:
             filters.append(
-                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
+                "allowed users: " & ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
         if repo_config.allowed_teams:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7837bbd22c80>
mock_github_client = <MagicMock name='github_client' id='132180862377264'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7837bbd22c80>
repo_config = <stampbot.config.RepoConfig object at 0x7837bbb35910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
>               "allowed users: " & ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
E           TypeError: unsupported operand type(s) for &: 'str' and 'str'

stampbot/webhook_handler.py:876: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:21:41.717116Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:21:41.717116Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for &: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 5, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -877,7 +877,7 @@
             )
         if repo_config.allowed_teams:
             filters.append(
-                "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
+                "allowed teams: " & ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
         if filters:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x757776b06c80>
mock_github_client = <MagicMock name='github_client' id='129155982843184'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x757776b06c80>
repo_config = <stampbot.config.RepoConfig object at 0x757776d21910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
        if repo_config.allowed_teams:
            filters.append(
>               "allowed teams: " & ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
            )
E           TypeError: unsupported operand type(s) for &: 'str' and 'str'

stampbot/webhook_handler.py:880: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:18:48.834775Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:18:48.834775Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for &: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.90s
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -115,7 +115,7 @@
             return False
 
         expected_signature = (
-            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
+            "sha256=" ^ hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
         )
 
         try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x79349ff969c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x79349f04af90>
payload = b'{"test": "data"}', signature = 'sha256=invalid'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
>           "sha256=" ^ hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
E       TypeError: unsupported operand type(s) for ^: 'str' and 'str'

stampbot/webhook_handler.py:118: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:44:10.394322Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: unsupported operand type(s) for ^: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 1, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if command in repo_config.approve_commands ^ repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ea79d995010>
mock_github_client = <MagicMock name='github_client' id='139258384882992'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7ea79d995010>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
            command = command_match.group(1).lower()
            add_span_attributes(span, {"chatops.command": command})
    
            if command == "help":
                success = await self._post_help(
                    installation_id,
                    repo_full_name,
                    pr_number,
                    repo_config,
                )
    
                chatops_commands_total.labels(
                    command="help",
                    status="success" if success else "failure",
                ).inc()
    
                add_span_attributes(
                    span, {"chatops.result": "help_posted" if success else "help_failed"}
                )
                set_span_ok(span)
    
                return {
                    "status": "success" if success else "error",
                    "message": "Help message posted" if success else "Failed to post help message",
                }
    
>           if command in repo_config.approve_commands ^ repo_config.unapprove_commands:
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for ^: 'list' and 'list'

stampbot/webhook_handler.py:730: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:25:06.589106Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:25:06.589565Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:25:06.589106Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:25:06.589565Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - TypeError: unsupported operand type(s) for ^: 'list' and 'list'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.45s
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 2, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -864,7 +864,7 @@
         if repo_config.required_labels:
             filters.append(
                 "required labels: "
-                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
+                ^ ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
         if repo_config.required_title_patterns:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x72926f8cad60>
mock_github_client = <MagicMock name='github_client' id='125973286411568'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x72926f8cad60>
repo_config = <stampbot.config.RepoConfig object at 0x729270d91a90>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
>               "required labels: "
                ^ ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
E           TypeError: unsupported operand type(s) for ^: 'str' and 'str'

stampbot/webhook_handler.py:866: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:32:51.061665Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:32:51.061665Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for ^: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.91s
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 3, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -869,7 +869,7 @@
         if repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
-                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
+                ^ ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
         if repo_config.allowed_users:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x717ccb662c80>
mock_github_client = <MagicMock name='github_client' id='124780825664816'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x717ccb662c80>
repo_config = <stampbot.config.RepoConfig object at 0x717ccca65910>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
>               "required title patterns: "
                ^ ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
E           TypeError: unsupported operand type(s) for ^: 'str' and 'str'

stampbot/webhook_handler.py:871: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:23:22.175079Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:23:22.175079Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for ^: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.88s
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 4, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -873,7 +873,7 @@
             )
         if repo_config.allowed_users:
             filters.append(
-                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
+                "allowed users: " ^ ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
         if repo_config.allowed_teams:
             filters.append(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7b7774f32cf0>
mock_github_client = <MagicMock name='github_client' id='135753017040176'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7b7774f32cf0>
repo_config = <stampbot.config.RepoConfig object at 0x7b7774f39a90>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
>               "allowed users: " ^ ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
E           TypeError: unsupported operand type(s) for ^: 'str' and 'str'

stampbot/webhook_handler.py:876: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:57:44.153804Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:57:44.153804Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for ^: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.88s
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 5, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -877,7 +877,7 @@
             )
         if repo_config.allowed_teams:
             filters.append(
-                "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
+                "allowed teams: " ^ ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
         if filters:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x76ff77236d60>
mock_github_client = <MagicMock name='github_client' id='130839610072368'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1000: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:708: in _handle_pr_comment
    success = await self._post_help(
stampbot/webhook_handler.py:912: in _post_help
    self._format_help_message(repo_config),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x76ff77236d60>
repo_config = <stampbot.config.RepoConfig object at 0x76ff76f59a90>

    def _format_help_message(self, repo_config: RepoConfig) -> str:
        """Format a help message for the effective repository configuration.
    
        Args:
            repo_config: Repository configuration
    
        Returns:
            Markdown help text.
        """
        lines = ["## Stampbot Help", ""]
    
        lines.extend(
            [
                "### ChatOps Commands",
                "",
                (
                    f"Approve and unapprove commands require "
                    f"**{repo_config.chatops_required_permission}** permission or higher."
                ),
                "",
            ]
        )
    
        approve_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.approve_commands
        )
        unapprove_commands = ", ".join(
            f"`@stampbot {command}`" for command in repo_config.unapprove_commands
        )
        lines.extend(
            [
                f"- **Approve**: {approve_commands}",
                f"- **Unapprove**: {unapprove_commands}",
                "- **Help**: `@stampbot help`",
                "",
                "### Label-Based Auto-Approval",
                "",
            ]
        )
    
        if repo_config.auto_approve_on_label:
            approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
            lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
        else:
            lines.append("Label-based auto-approval is disabled in this repository.")
    
        filters = []
        if repo_config.required_labels:
            filters.append(
                "required labels: "
                + ", ".join(f"`{label}`" for label in repo_config.required_labels)
            )
        if repo_config.required_title_patterns:
            filters.append(
                "required title patterns: "
                + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
            )
        if repo_config.allowed_users:
            filters.append(
                "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
            )
        if repo_config.allowed_teams:
            filters.append(
>               "allowed teams: " ^ ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
            )
E           TypeError: unsupported operand type(s) for ^: 'str' and 'str'

stampbot/webhook_handler.py:880: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:03:34.360393Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:03:34.360393Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - TypeError: unsupported operand type(s) for ^: 'str' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.86s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 0, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1001,7 +1001,7 @@
                 comment,
             )
 
-            duration = time.time() - start_time
+            duration = time.time() + start_time
             pr_approval_duration_seconds.observe(duration)
 
             status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1096,7 +1096,7 @@
                         pr_number,
                         extra={"repo": repo_full_name, "pr_number": pr_number},
                     )
-                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
+                    pr_dismissal_duration_seconds.observe(time.time() + start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
                     return True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1114,7 +1114,7 @@
                     )
                     success = success and result
 
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
                 status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.31s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 3, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1126,7 +1126,7 @@
                 return success
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 0, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1001,7 +1001,7 @@
                 comment,
             )
 
-            duration = time.time() - start_time
+            duration = time.time() * start_time
             pr_approval_duration_seconds.observe(duration)
 
             status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1096,7 +1096,7 @@
                         pr_number,
                         extra={"repo": repo_full_name, "pr_number": pr_number},
                     )
-                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
+                    pr_dismissal_duration_seconds.observe(time.time() * start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
                     return True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1114,7 +1114,7 @@
                     )
                     success = success and result
 
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
                 status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 3, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1126,7 +1126,7 @@
                 return success
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 0, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1001,7 +1001,7 @@
                 comment,
             )
 
-            duration = time.time() - start_time
+            duration = time.time() / start_time
             pr_approval_duration_seconds.observe(duration)
 
             status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1096,7 +1096,7 @@
                         pr_number,
                         extra={"repo": repo_full_name, "pr_number": pr_number},
                     )
-                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
+                    pr_dismissal_duration_seconds.observe(time.time() / start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
                     return True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1114,7 +1114,7 @@
                     )
                     success = success and result
 
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
                 status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 3, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1126,7 +1126,7 @@
                 return success
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 0, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1001,7 +1001,7 @@
                 comment,
             )
 
-            duration = time.time() - start_time
+            duration = time.time() // start_time
             pr_approval_duration_seconds.observe(duration)
 
             status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1096,7 +1096,7 @@
                         pr_number,
                         extra={"repo": repo_full_name, "pr_number": pr_number},
                     )
-                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
+                    pr_dismissal_duration_seconds.observe(time.time() // start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
                     return True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1114,7 +1114,7 @@
                     )
                     success = success and result
 
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
                 status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 3, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1126,7 +1126,7 @@
                 return success
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 0, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1001,7 +1001,7 @@
                 comment,
             )
 
-            duration = time.time() - start_time
+            duration = time.time() % start_time
             pr_approval_duration_seconds.observe(duration)
 
             status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1096,7 +1096,7 @@
                         pr_number,
                         extra={"repo": repo_full_name, "pr_number": pr_number},
                     )
-                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
+                    pr_dismissal_duration_seconds.observe(time.time() % start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
                     return True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1114,7 +1114,7 @@
                     )
                     success = success and result
 
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
                 status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 3, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1126,7 +1126,7 @@
                 return success
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 0, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1001,7 +1001,7 @@
                 comment,
             )
 
-            duration = time.time() - start_time
+            duration = time.time() ** start_time
             pr_approval_duration_seconds.observe(duration)
 
             status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x79f99e72d0f0>
mock_github_client = <MagicMock name='github_client' id='134113034245456'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x79f99e72d0f0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label', skip_existing_check = False, head_sha = 'abc123def456'

    async def _approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str,
        trigger_type: str,
        skip_existing_check: bool = False,
        head_sha: str | None = None,
    ) -> bool:
        """Approve a PR and track metrics.
    
        Checks for existing active approvals first to avoid duplicate reviews.
        Two replicas handling the ``opened`` and ``labeled`` events of one new
        pull request can both pass that check, so after a successful approval
        the reviews are read again and every Stampbot approval of the same head
        except the oldest is dismissed. Approving twice and dismissing one is
        preferred to deferring, which could miss an approval when the other
        event never arrives.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            comment: Approval comment
            trigger_type: What triggered the approval (label, chatops)
            skip_existing_check: Whether to skip duplicate active approval detection
            head_sha: Current PR head SHA, used to distinguish stale approvals
    
        Returns:
            True if successful (or already approved)
        """
        with create_span(
            "webhook.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "approval.trigger_type": trigger_type,
            },
        ) as span:
            if not skip_existing_check:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Unknown state. A duplicate is recoverable; a missed
                    # approval is not, so approve and let the cleanup sort it out.
                    logger.warning(
                        "Could not read reviews for PR #%d in %s; approving without a check",
                        pr_number,
                        repo_full_name,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    reviews = []
                existing_approvals = self._active_approvals_for_head(reviews, head_sha)
                if existing_approvals:
                    logger.info(
                        "PR #%d in %s already has active approval for current head, skipping",
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "existing_review_ids": existing_approvals,
                        },
                    )
                    add_span_attributes(
                        span,
                        {
                            "approval.result": "already_approved",
                            "approval.existing_reviews": len(existing_approvals),
                        },
                    )
                    set_span_ok(span)
                    return True
    
            start_time = time.time()
    
            success = await run_in_threadpool(
                github_client.approve_pr,
                installation_id,
                repo_full_name,
                pr_number,
                comment,
            )
    
>           duration = time.time() ** start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^^
E           OverflowError: (34, 'Numerical result out of range')

stampbot/webhook_handler.py:1004: OverflowError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:15:59.056528Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:15:59.057470Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:15:59.057697Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:15:59.056528Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:15:59.057470Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:15:59.057697Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1096,7 +1096,7 @@
                         pr_number,
                         extra={"repo": repo_full_name, "pr_number": pr_number},
                     )
-                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
+                    pr_dismissal_duration_seconds.observe(time.time() ** start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
                     return True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.........................................................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x70c26c6b7af0>
mock_github_client = <MagicMock name='github_client' id='123980367120096'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
        """Test removing label when no bot reviews exist."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = []  # No reviews to dismiss
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:700: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:27:52.079248Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:27:52.079650Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:27:52.079738Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "(34, 'Numerical result out of range')"}, "event": "Error dismissing approvals: (34, 'Numerical result out of range')", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T03:27:52.079922Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:27:52.079248Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:27:52.079650Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:27:52.079738Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "(34, 'Numerical result out of range')"}, 'event': "Error dismissing approvals: (34, 'Numerical result out of range')", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T03:27:52.079922Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 417 passed, 3 deselected, 1 warning in 2.50s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1114,7 +1114,7 @@
                     )
                     success = success and result
 
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
                 status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......................................................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7cfba8605a20>
mock_github_client = <MagicMock name='github_client' id='137420324797952'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
        """Test removing autoapprove label dismisses approvals."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"},
            {"id": 222, "state": "APPROVED", "commit_id": "abc123def456"},
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:674: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:35:36.522321Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:35:36.522719Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:35:36.522819Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "(34, 'Numerical result out of range')"}, "event": "Error dismissing approvals: (34, 'Numerical result out of range')", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T03:35:36.523211Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:35:36.522321Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:35:36.522719Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:35:36.522819Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "(34, 'Numerical result out of range')"}, 'event': "Error dismissing approvals: (34, 'Numerical result out of range')", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T03:35:36.523211Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 415 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 3, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1126,7 +1126,7 @@
                 return success
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
....F
=================================== FAILURES ===================================
___ test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] ____

self = <stampbot.webhook_handler.WebhookHandler object at 0x701017eb3f00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
>               reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )

stampbot/webhook_handler.py:1072: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:34: in run_in_threadpool
    return await anyio.to_thread.run_sync(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:65: in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2779: in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:1124: in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.find_bot_approval_reviews' id='123214513010544'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   RuntimeError: GitHub unavailable

/usr/lib/python3.14/unittest/mock.py:1245: RuntimeError

During handling of the above exception, another exception occurred:

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x701017eb3f00>
mock_github_client = <MagicMock name='github_client' id='123214513009200'>
failure = RuntimeError('GitHub unavailable')

    @pytest.mark.asyncio
    @pytest.mark.parametrize("failure", [RuntimeError("GitHub unavailable"), None])
    async def test_pr_duplicate_check_failure_keeps_approval_and_is_counted(
        webhook_handler, mock_github_client, failure
    ):
        """Test a failed duplicate scan keeps the approval and reaches the failure counter.
    
        The client reports a listing failure as None; an unexpected exception is
        handled the same way.
        """
        from stampbot.metrics import pr_dismissals_total
    
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [[], failure]
        failures = pr_dismissals_total.labels(trigger_type="duplicate", status="failure")
        before = failures._value.get()
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:166: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
stampbot/webhook_handler.py:1019: in _approve_pr
    await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x701017eb3f00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Nothing can be dismissed without the list, and the
                    # failure has to reach the metric operators watch.
                    raise RuntimeError("Stampbot reviews could not be listed")
                if keep_oldest:
                    # Duplicate cleanup only touches approvals proven to be of
                    # this head, and leaves the oldest of them in place.
                    review_ids = self._active_approvals_for_head(
                        reviews, head_sha, proven_only=True
                    )[1:]
                else:
                    review_ids = self._active_approvals_for_head(reviews, head_sha)
    
                add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
    
                if not review_ids:
                    logger.debug(
                        "No Stampbot approvals to dismiss on PR #%d",
                        pr_number,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
                    add_span_attributes(span, {"dismissal.result": "no_reviews"})
                    set_span_ok(span)
                    return True
    
                # Dismiss each review
                success = True
                for review_id in review_ids:
                    result = await run_in_threadpool(
                        github_client.dismiss_approval,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        review_id,
                        message,
                    )
                    success = success and result
    
                duration = time.time() - start_time
                pr_dismissal_duration_seconds.observe(duration)
    
                status = "success" if success else "failure"
                pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
    
                add_span_attributes(span, {"dismissal.result": status})
                set_span_ok(span)
    
                return success
    
            except Exception as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/webhook_handler.py:1129: OverflowError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:16:14.057017Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:16:14.057450Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:16:14.057639Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:16:14.057017Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:16:14.057450Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:16:14.057639Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 364 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 0, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1001,7 +1001,7 @@
                 comment,
             )
 
-            duration = time.time() - start_time
+            duration = time.time() >> start_time
             pr_approval_duration_seconds.observe(duration)
 
             status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7841d396d0f0>
mock_github_client = <MagicMock name='github_client' id='132224139859280'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7841d396d0f0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label', skip_existing_check = False, head_sha = 'abc123def456'

    async def _approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str,
        trigger_type: str,
        skip_existing_check: bool = False,
        head_sha: str | None = None,
    ) -> bool:
        """Approve a PR and track metrics.
    
        Checks for existing active approvals first to avoid duplicate reviews.
        Two replicas handling the ``opened`` and ``labeled`` events of one new
        pull request can both pass that check, so after a successful approval
        the reviews are read again and every Stampbot approval of the same head
        except the oldest is dismissed. Approving twice and dismissing one is
        preferred to deferring, which could miss an approval when the other
        event never arrives.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            comment: Approval comment
            trigger_type: What triggered the approval (label, chatops)
            skip_existing_check: Whether to skip duplicate active approval detection
            head_sha: Current PR head SHA, used to distinguish stale approvals
    
        Returns:
            True if successful (or already approved)
        """
        with create_span(
            "webhook.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "approval.trigger_type": trigger_type,
            },
        ) as span:
            if not skip_existing_check:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Unknown state. A duplicate is recoverable; a missed
                    # approval is not, so approve and let the cleanup sort it out.
                    logger.warning(
                        "Could not read reviews for PR #%d in %s; approving without a check",
                        pr_number,
                        repo_full_name,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    reviews = []
                existing_approvals = self._active_approvals_for_head(reviews, head_sha)
                if existing_approvals:
                    logger.info(
                        "PR #%d in %s already has active approval for current head, skipping",
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "existing_review_ids": existing_approvals,
                        },
                    )
                    add_span_attributes(
                        span,
                        {
                            "approval.result": "already_approved",
                            "approval.existing_reviews": len(existing_approvals),
                        },
                    )
                    set_span_ok(span)
                    return True
    
            start_time = time.time()
    
            success = await run_in_threadpool(
                github_client.approve_pr,
                installation_id,
                repo_full_name,
                pr_number,
                comment,
            )
    
>           duration = time.time() >> start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/webhook_handler.py:1004: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:44:12.959732Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:44:12.960796Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:44:12.961196Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:44:12.959732Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:44:12.960796Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:44:12.961196Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1096,7 +1096,7 @@
                         pr_number,
                         extra={"repo": repo_full_name, "pr_number": pr_number},
                     )
-                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
+                    pr_dismissal_duration_seconds.observe(time.time() >> start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
                     return True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.........................................................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7638c0813310>
mock_github_client = <MagicMock name='github_client' id='129986139817696'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
        """Test removing label when no bot reviews exist."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = []  # No reviews to dismiss
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:700: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:34:20.362406Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:34:20.362812Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:34:20.362905Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "unsupported operand type(s) for >>: 'float' and 'float'"}, "event": "Error dismissing approvals: unsupported operand type(s) for >>: 'float' and 'float'", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T03:34:20.363091Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:34:20.362406Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:34:20.362812Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:34:20.362905Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for >>: 'float' and 'float'", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T03:34:20.363091Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 417 passed, 3 deselected, 1 warning in 2.50s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1114,7 +1114,7 @@
                     )
                     success = success and result
 
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
                 status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......................................................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x78fa36e59a90>
mock_github_client = <MagicMock name='github_client' id='133016077942272'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
        """Test removing autoapprove label dismisses approvals."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"},
            {"id": 222, "state": "APPROVED", "commit_id": "abc123def456"},
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:674: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:31:40.617502Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:31:40.617906Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:31:40.618004Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "unsupported operand type(s) for >>: 'float' and 'float'"}, "event": "Error dismissing approvals: unsupported operand type(s) for >>: 'float' and 'float'", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T03:31:40.618406Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:31:40.617502Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:31:40.617906Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:31:40.618004Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for >>: 'float' and 'float'", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T03:31:40.618406Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 415 passed, 3 deselected, 1 warning in 2.51s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 3, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1126,7 +1126,7 @@
                 return success
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
....F
=================================== FAILURES ===================================
___ test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] ____

self = <stampbot.webhook_handler.WebhookHandler object at 0x7fb53a4a7f00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
>               reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )

stampbot/webhook_handler.py:1072: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:34: in run_in_threadpool
    return await anyio.to_thread.run_sync(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:65: in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2779: in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:1124: in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.find_bot_approval_reviews' id='140416365623152'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   RuntimeError: GitHub unavailable

/usr/lib/python3.14/unittest/mock.py:1245: RuntimeError

During handling of the above exception, another exception occurred:

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fb53a4a7f00>
mock_github_client = <MagicMock name='github_client' id='140416365621808'>
failure = RuntimeError('GitHub unavailable')

    @pytest.mark.asyncio
    @pytest.mark.parametrize("failure", [RuntimeError("GitHub unavailable"), None])
    async def test_pr_duplicate_check_failure_keeps_approval_and_is_counted(
        webhook_handler, mock_github_client, failure
    ):
        """Test a failed duplicate scan keeps the approval and reaches the failure counter.
    
        The client reports a listing failure as None; an unexpected exception is
        handled the same way.
        """
        from stampbot.metrics import pr_dismissals_total
    
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [[], failure]
        failures = pr_dismissals_total.labels(trigger_type="duplicate", status="failure")
        before = failures._value.get()
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:166: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
stampbot/webhook_handler.py:1019: in _approve_pr
    await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7fb53a4a7f00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Nothing can be dismissed without the list, and the
                    # failure has to reach the metric operators watch.
                    raise RuntimeError("Stampbot reviews could not be listed")
                if keep_oldest:
                    # Duplicate cleanup only touches approvals proven to be of
                    # this head, and leaves the oldest of them in place.
                    review_ids = self._active_approvals_for_head(
                        reviews, head_sha, proven_only=True
                    )[1:]
                else:
                    review_ids = self._active_approvals_for_head(reviews, head_sha)
    
                add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
    
                if not review_ids:
                    logger.debug(
                        "No Stampbot approvals to dismiss on PR #%d",
                        pr_number,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
                    add_span_attributes(span, {"dismissal.result": "no_reviews"})
                    set_span_ok(span)
                    return True
    
                # Dismiss each review
                success = True
                for review_id in review_ids:
                    result = await run_in_threadpool(
                        github_client.dismiss_approval,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        review_id,
                        message,
                    )
                    success = success and result
    
                duration = time.time() - start_time
                pr_dismissal_duration_seconds.observe(duration)
    
                status = "success" if success else "failure"
                pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
    
                add_span_attributes(span, {"dismissal.result": status})
                set_span_ok(span)
    
                return success
    
            except Exception as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/webhook_handler.py:1129: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:28:14.053439Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:28:14.053848Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:28:14.054032Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:28:14.053439Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:28:14.053848Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:28:14.054032Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 364 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 0, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1001,7 +1001,7 @@
                 comment,
             )
 
-            duration = time.time() - start_time
+            duration = time.time() << start_time
             pr_approval_duration_seconds.observe(duration)
 
             status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74252c6090f0>
mock_github_client = <MagicMock name='github_client' id='127703033402704'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x74252c6090f0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label', skip_existing_check = False, head_sha = 'abc123def456'

    async def _approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str,
        trigger_type: str,
        skip_existing_check: bool = False,
        head_sha: str | None = None,
    ) -> bool:
        """Approve a PR and track metrics.
    
        Checks for existing active approvals first to avoid duplicate reviews.
        Two replicas handling the ``opened`` and ``labeled`` events of one new
        pull request can both pass that check, so after a successful approval
        the reviews are read again and every Stampbot approval of the same head
        except the oldest is dismissed. Approving twice and dismissing one is
        preferred to deferring, which could miss an approval when the other
        event never arrives.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            comment: Approval comment
            trigger_type: What triggered the approval (label, chatops)
            skip_existing_check: Whether to skip duplicate active approval detection
            head_sha: Current PR head SHA, used to distinguish stale approvals
    
        Returns:
            True if successful (or already approved)
        """
        with create_span(
            "webhook.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "approval.trigger_type": trigger_type,
            },
        ) as span:
            if not skip_existing_check:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Unknown state. A duplicate is recoverable; a missed
                    # approval is not, so approve and let the cleanup sort it out.
                    logger.warning(
                        "Could not read reviews for PR #%d in %s; approving without a check",
                        pr_number,
                        repo_full_name,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    reviews = []
                existing_approvals = self._active_approvals_for_head(reviews, head_sha)
                if existing_approvals:
                    logger.info(
                        "PR #%d in %s already has active approval for current head, skipping",
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "existing_review_ids": existing_approvals,
                        },
                    )
                    add_span_attributes(
                        span,
                        {
                            "approval.result": "already_approved",
                            "approval.existing_reviews": len(existing_approvals),
                        },
                    )
                    set_span_ok(span)
                    return True
    
            start_time = time.time()
    
            success = await run_in_threadpool(
                github_client.approve_pr,
                installation_id,
                repo_full_name,
                pr_number,
                comment,
            )
    
>           duration = time.time() << start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/webhook_handler.py:1004: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:55:11.613192Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:55:11.613816Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:55:11.614042Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:55:11.613192Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:55:11.613816Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:55:11.614042Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.40s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1096,7 +1096,7 @@
                         pr_number,
                         extra={"repo": repo_full_name, "pr_number": pr_number},
                     )
-                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
+                    pr_dismissal_duration_seconds.observe(time.time() << start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
                     return True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.........................................................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75b084170050>
mock_github_client = <MagicMock name='github_client' id='129401018631904'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
        """Test removing label when no bot reviews exist."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = []  # No reviews to dismiss
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:700: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:57:38.346668Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:57:38.347070Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:57:38.347163Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "unsupported operand type(s) for <<: 'float' and 'float'"}, "event": "Error dismissing approvals: unsupported operand type(s) for <<: 'float' and 'float'", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T03:57:38.347381Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:57:38.346668Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:57:38.347070Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:57:38.347163Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for <<: 'float' and 'float'", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T03:57:38.347381Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 417 passed, 3 deselected, 1 warning in 2.49s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1114,7 +1114,7 @@
                     )
                     success = success and result
 
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
                 status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......................................................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x72f56d454c90>
mock_github_client = <MagicMock name='github_client' id='126398445600256'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
        """Test removing autoapprove label dismisses approvals."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"},
            {"id": 222, "state": "APPROVED", "commit_id": "abc123def456"},
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:674: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:12:09.635970Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:12:09.636390Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:12:09.636485Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "unsupported operand type(s) for <<: 'float' and 'float'"}, "event": "Error dismissing approvals: unsupported operand type(s) for <<: 'float' and 'float'", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T03:12:09.636843Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:12:09.635970Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:12:09.636390Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:12:09.636485Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for <<: 'float' and 'float'", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T03:12:09.636843Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 415 passed, 3 deselected, 1 warning in 2.52s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 3, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1126,7 +1126,7 @@
                 return success
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
....F
=================================== FAILURES ===================================
___ test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] ____

self = <stampbot.webhook_handler.WebhookHandler object at 0x7dac516abf00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
>               reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )

stampbot/webhook_handler.py:1072: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:34: in run_in_threadpool
    return await anyio.to_thread.run_sync(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:65: in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2779: in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:1124: in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.find_bot_approval_reviews' id='138179075667824'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   RuntimeError: GitHub unavailable

/usr/lib/python3.14/unittest/mock.py:1245: RuntimeError

During handling of the above exception, another exception occurred:

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7dac516abf00>
mock_github_client = <MagicMock name='github_client' id='138179075666480'>
failure = RuntimeError('GitHub unavailable')

    @pytest.mark.asyncio
    @pytest.mark.parametrize("failure", [RuntimeError("GitHub unavailable"), None])
    async def test_pr_duplicate_check_failure_keeps_approval_and_is_counted(
        webhook_handler, mock_github_client, failure
    ):
        """Test a failed duplicate scan keeps the approval and reaches the failure counter.
    
        The client reports a listing failure as None; an unexpected exception is
        handled the same way.
        """
        from stampbot.metrics import pr_dismissals_total
    
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [[], failure]
        failures = pr_dismissals_total.labels(trigger_type="duplicate", status="failure")
        before = failures._value.get()
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:166: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
stampbot/webhook_handler.py:1019: in _approve_pr
    await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7dac516abf00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Nothing can be dismissed without the list, and the
                    # failure has to reach the metric operators watch.
                    raise RuntimeError("Stampbot reviews could not be listed")
                if keep_oldest:
                    # Duplicate cleanup only touches approvals proven to be of
                    # this head, and leaves the oldest of them in place.
                    review_ids = self._active_approvals_for_head(
                        reviews, head_sha, proven_only=True
                    )[1:]
                else:
                    review_ids = self._active_approvals_for_head(reviews, head_sha)
    
                add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
    
                if not review_ids:
                    logger.debug(
                        "No Stampbot approvals to dismiss on PR #%d",
                        pr_number,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
                    add_span_attributes(span, {"dismissal.result": "no_reviews"})
                    set_span_ok(span)
                    return True
    
                # Dismiss each review
                success = True
                for review_id in review_ids:
                    result = await run_in_threadpool(
                        github_client.dismiss_approval,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        review_id,
                        message,
                    )
                    success = success and result
    
                duration = time.time() - start_time
                pr_dismissal_duration_seconds.observe(duration)
    
                status = "success" if success else "failure"
                pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
    
                add_span_attributes(span, {"dismissal.result": status})
                set_span_ok(span)
    
                return success
    
            except Exception as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/webhook_handler.py:1129: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:31:04.891185Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:31:04.891595Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:31:04.891779Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:31:04.891185Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:31:04.891595Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:31:04.891779Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 364 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 0, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1001,7 +1001,7 @@
                 comment,
             )
 
-            duration = time.time() - start_time
+            duration = time.time() | start_time
             pr_approval_duration_seconds.observe(duration)
 
             status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a01b51010f0>
mock_github_client = <MagicMock name='github_client' id='134147772355920'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7a01b51010f0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label', skip_existing_check = False, head_sha = 'abc123def456'

    async def _approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str,
        trigger_type: str,
        skip_existing_check: bool = False,
        head_sha: str | None = None,
    ) -> bool:
        """Approve a PR and track metrics.
    
        Checks for existing active approvals first to avoid duplicate reviews.
        Two replicas handling the ``opened`` and ``labeled`` events of one new
        pull request can both pass that check, so after a successful approval
        the reviews are read again and every Stampbot approval of the same head
        except the oldest is dismissed. Approving twice and dismissing one is
        preferred to deferring, which could miss an approval when the other
        event never arrives.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            comment: Approval comment
            trigger_type: What triggered the approval (label, chatops)
            skip_existing_check: Whether to skip duplicate active approval detection
            head_sha: Current PR head SHA, used to distinguish stale approvals
    
        Returns:
            True if successful (or already approved)
        """
        with create_span(
            "webhook.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "approval.trigger_type": trigger_type,
            },
        ) as span:
            if not skip_existing_check:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Unknown state. A duplicate is recoverable; a missed
                    # approval is not, so approve and let the cleanup sort it out.
                    logger.warning(
                        "Could not read reviews for PR #%d in %s; approving without a check",
                        pr_number,
                        repo_full_name,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    reviews = []
                existing_approvals = self._active_approvals_for_head(reviews, head_sha)
                if existing_approvals:
                    logger.info(
                        "PR #%d in %s already has active approval for current head, skipping",
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "existing_review_ids": existing_approvals,
                        },
                    )
                    add_span_attributes(
                        span,
                        {
                            "approval.result": "already_approved",
                            "approval.existing_reviews": len(existing_approvals),
                        },
                    )
                    set_span_ok(span)
                    return True
    
            start_time = time.time()
    
            success = await run_in_threadpool(
                github_client.approve_pr,
                installation_id,
                repo_full_name,
                pr_number,
                comment,
            )
    
>           duration = time.time() | start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/webhook_handler.py:1004: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:53.307934Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:42:53.308587Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:53.309079Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:53.307934Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:42:53.308587Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:53.309079Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.40s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1096,7 +1096,7 @@
                         pr_number,
                         extra={"repo": repo_full_name, "pr_number": pr_number},
                     )
-                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
+                    pr_dismissal_duration_seconds.observe(time.time() | start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
                     return True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.........................................................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7affe743baf0>
mock_github_client = <MagicMock name='github_client' id='135239536953056'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
        """Test removing label when no bot reviews exist."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = []  # No reviews to dismiss
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:700: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:18.878279Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:18:18.878693Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:18.878789Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "unsupported operand type(s) for |: 'float' and 'float'"}, "event": "Error dismissing approvals: unsupported operand type(s) for |: 'float' and 'float'", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T04:18:18.878980Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:18.878279Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:18:18.878693Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:18.878789Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for |: 'float' and 'float'", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T04:18:18.878980Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 417 passed, 3 deselected, 1 warning in 2.53s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1114,7 +1114,7 @@
                     )
                     success = success and result
 
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
                 status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......................................................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x785e4cbc3230>
mock_github_client = <MagicMock name='github_client' id='132346431077888'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
        """Test removing autoapprove label dismisses approvals."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"},
            {"id": 222, "state": "APPROVED", "commit_id": "abc123def456"},
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:674: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:30:15.951339Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:30:15.951760Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:30:15.951852Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "unsupported operand type(s) for |: 'float' and 'float'"}, "event": "Error dismissing approvals: unsupported operand type(s) for |: 'float' and 'float'", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T04:30:15.952223Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:30:15.951339Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:30:15.951760Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:30:15.951852Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for |: 'float' and 'float'", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T04:30:15.952223Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 415 passed, 3 deselected, 1 warning in 2.51s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 3, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1126,7 +1126,7 @@
                 return success
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
....F
=================================== FAILURES ===================================
___ test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] ____

self = <stampbot.webhook_handler.WebhookHandler object at 0x739ceb2a7f00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
>               reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )

stampbot/webhook_handler.py:1072: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:34: in run_in_threadpool
    return await anyio.to_thread.run_sync(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:65: in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2779: in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:1124: in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.find_bot_approval_reviews' id='127117819358448'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   RuntimeError: GitHub unavailable

/usr/lib/python3.14/unittest/mock.py:1245: RuntimeError

During handling of the above exception, another exception occurred:

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x739ceb2a7f00>
mock_github_client = <MagicMock name='github_client' id='127117819356768'>
failure = RuntimeError('GitHub unavailable')

    @pytest.mark.asyncio
    @pytest.mark.parametrize("failure", [RuntimeError("GitHub unavailable"), None])
    async def test_pr_duplicate_check_failure_keeps_approval_and_is_counted(
        webhook_handler, mock_github_client, failure
    ):
        """Test a failed duplicate scan keeps the approval and reaches the failure counter.
    
        The client reports a listing failure as None; an unexpected exception is
        handled the same way.
        """
        from stampbot.metrics import pr_dismissals_total
    
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [[], failure]
        failures = pr_dismissals_total.labels(trigger_type="duplicate", status="failure")
        before = failures._value.get()
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:166: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
stampbot/webhook_handler.py:1019: in _approve_pr
    await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x739ceb2a7f00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Nothing can be dismissed without the list, and the
                    # failure has to reach the metric operators watch.
                    raise RuntimeError("Stampbot reviews could not be listed")
                if keep_oldest:
                    # Duplicate cleanup only touches approvals proven to be of
                    # this head, and leaves the oldest of them in place.
                    review_ids = self._active_approvals_for_head(
                        reviews, head_sha, proven_only=True
                    )[1:]
                else:
                    review_ids = self._active_approvals_for_head(reviews, head_sha)
    
                add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
    
                if not review_ids:
                    logger.debug(
                        "No Stampbot approvals to dismiss on PR #%d",
                        pr_number,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
                    add_span_attributes(span, {"dismissal.result": "no_reviews"})
                    set_span_ok(span)
                    return True
    
                # Dismiss each review
                success = True
                for review_id in review_ids:
                    result = await run_in_threadpool(
                        github_client.dismiss_approval,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        review_id,
                        message,
                    )
                    success = success and result
    
                duration = time.time() - start_time
                pr_dismissal_duration_seconds.observe(duration)
    
                status = "success" if success else "failure"
                pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
    
                add_span_attributes(span, {"dismissal.result": status})
                set_span_ok(span)
    
                return success
    
            except Exception as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/webhook_handler.py:1129: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:17:34.764697Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:17:34.765098Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:17:34.765306Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:17:34.764697Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:17:34.765098Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:17:34.765306Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 364 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 0, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1001,7 +1001,7 @@
                 comment,
             )
 
-            duration = time.time() - start_time
+            duration = time.time() & start_time
             pr_approval_duration_seconds.observe(duration)
 
             status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x767117fc50f0>
mock_github_client = <MagicMock name='github_client' id='130228195202384'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x767117fc50f0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label', skip_existing_check = False, head_sha = 'abc123def456'

    async def _approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str,
        trigger_type: str,
        skip_existing_check: bool = False,
        head_sha: str | None = None,
    ) -> bool:
        """Approve a PR and track metrics.
    
        Checks for existing active approvals first to avoid duplicate reviews.
        Two replicas handling the ``opened`` and ``labeled`` events of one new
        pull request can both pass that check, so after a successful approval
        the reviews are read again and every Stampbot approval of the same head
        except the oldest is dismissed. Approving twice and dismissing one is
        preferred to deferring, which could miss an approval when the other
        event never arrives.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            comment: Approval comment
            trigger_type: What triggered the approval (label, chatops)
            skip_existing_check: Whether to skip duplicate active approval detection
            head_sha: Current PR head SHA, used to distinguish stale approvals
    
        Returns:
            True if successful (or already approved)
        """
        with create_span(
            "webhook.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "approval.trigger_type": trigger_type,
            },
        ) as span:
            if not skip_existing_check:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Unknown state. A duplicate is recoverable; a missed
                    # approval is not, so approve and let the cleanup sort it out.
                    logger.warning(
                        "Could not read reviews for PR #%d in %s; approving without a check",
                        pr_number,
                        repo_full_name,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    reviews = []
                existing_approvals = self._active_approvals_for_head(reviews, head_sha)
                if existing_approvals:
                    logger.info(
                        "PR #%d in %s already has active approval for current head, skipping",
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "existing_review_ids": existing_approvals,
                        },
                    )
                    add_span_attributes(
                        span,
                        {
                            "approval.result": "already_approved",
                            "approval.existing_reviews": len(existing_approvals),
                        },
                    )
                    set_span_ok(span)
                    return True
    
            start_time = time.time()
    
            success = await run_in_threadpool(
                github_client.approve_pr,
                installation_id,
                repo_full_name,
                pr_number,
                comment,
            )
    
>           duration = time.time() & start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/webhook_handler.py:1004: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:43:55.009265Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:43:55.009910Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:43:55.010136Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:43:55.009265Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:43:55.009910Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:43:55.010136Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1096,7 +1096,7 @@
                         pr_number,
                         extra={"repo": repo_full_name, "pr_number": pr_number},
                     )
-                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
+                    pr_dismissal_duration_seconds.observe(time.time() & start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
                     return True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.........................................................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7bf861a0baf0>
mock_github_client = <MagicMock name='github_client' id='136306741921504'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
        """Test removing label when no bot reviews exist."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = []  # No reviews to dismiss
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:700: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:31:56.526196Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:31:56.526588Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:31:56.526676Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "unsupported operand type(s) for &: 'float' and 'float'"}, "event": "Error dismissing approvals: unsupported operand type(s) for &: 'float' and 'float'", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T04:31:56.526846Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:31:56.526196Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:31:56.526588Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:31:56.526676Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for &: 'float' and 'float'", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T04:31:56.526846Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 417 passed, 3 deselected, 1 warning in 2.51s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1114,7 +1114,7 @@
                     )
                     success = success and result
 
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
                 status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......................................................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f5e8260bf50>
mock_github_client = <MagicMock name='github_client' id='140043912199680'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
        """Test removing autoapprove label dismisses approvals."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"},
            {"id": 222, "state": "APPROVED", "commit_id": "abc123def456"},
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:674: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:04:57.646619Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:04:57.647022Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:04:57.647116Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "unsupported operand type(s) for &: 'float' and 'float'"}, "event": "Error dismissing approvals: unsupported operand type(s) for &: 'float' and 'float'", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T04:04:57.647493Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:04:57.646619Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:04:57.647022Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:04:57.647116Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for &: 'float' and 'float'", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T04:04:57.647493Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 415 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 3, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1126,7 +1126,7 @@
                 return success
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
....F
=================================== FAILURES ===================================
___ test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] ____

self = <stampbot.webhook_handler.WebhookHandler object at 0x720f202c3f00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
>               reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )

stampbot/webhook_handler.py:1072: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:34: in run_in_threadpool
    return await anyio.to_thread.run_sync(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:65: in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2779: in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:1124: in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.find_bot_approval_reviews' id='125409311602544'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   RuntimeError: GitHub unavailable

/usr/lib/python3.14/unittest/mock.py:1245: RuntimeError

During handling of the above exception, another exception occurred:

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x720f202c3f00>
mock_github_client = <MagicMock name='github_client' id='125409311601200'>
failure = RuntimeError('GitHub unavailable')

    @pytest.mark.asyncio
    @pytest.mark.parametrize("failure", [RuntimeError("GitHub unavailable"), None])
    async def test_pr_duplicate_check_failure_keeps_approval_and_is_counted(
        webhook_handler, mock_github_client, failure
    ):
        """Test a failed duplicate scan keeps the approval and reaches the failure counter.
    
        The client reports a listing failure as None; an unexpected exception is
        handled the same way.
        """
        from stampbot.metrics import pr_dismissals_total
    
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [[], failure]
        failures = pr_dismissals_total.labels(trigger_type="duplicate", status="failure")
        before = failures._value.get()
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:166: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
stampbot/webhook_handler.py:1019: in _approve_pr
    await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x720f202c3f00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Nothing can be dismissed without the list, and the
                    # failure has to reach the metric operators watch.
                    raise RuntimeError("Stampbot reviews could not be listed")
                if keep_oldest:
                    # Duplicate cleanup only touches approvals proven to be of
                    # this head, and leaves the oldest of them in place.
                    review_ids = self._active_approvals_for_head(
                        reviews, head_sha, proven_only=True
                    )[1:]
                else:
                    review_ids = self._active_approvals_for_head(reviews, head_sha)
    
                add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
    
                if not review_ids:
                    logger.debug(
                        "No Stampbot approvals to dismiss on PR #%d",
                        pr_number,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
                    add_span_attributes(span, {"dismissal.result": "no_reviews"})
                    set_span_ok(span)
                    return True
    
                # Dismiss each review
                success = True
                for review_id in review_ids:
                    result = await run_in_threadpool(
                        github_client.dismiss_approval,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        review_id,
                        message,
                    )
                    success = success and result
    
                duration = time.time() - start_time
                pr_dismissal_duration_seconds.observe(duration)
    
                status = "success" if success else "failure"
                pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
    
                add_span_attributes(span, {"dismissal.result": status})
                set_span_ok(span)
    
                return success
    
            except Exception as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/webhook_handler.py:1129: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:13:36.951987Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:13:36.952410Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:13:36.952602Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:13:36.951987Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:13:36.952410Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:13:36.952602Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 364 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 0, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1001,7 +1001,7 @@
                 comment,
             )
 
-            duration = time.time() - start_time
+            duration = time.time() ^ start_time
             pr_approval_duration_seconds.observe(duration)
 
             status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a7d709c50f0>
mock_github_client = <MagicMock name='github_client' id='134679200060752'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7a7d709c50f0>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
comment = 'Auto-approved by Stampbot (label: autoapprove)'
trigger_type = 'label', skip_existing_check = False, head_sha = 'abc123def456'

    async def _approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str,
        trigger_type: str,
        skip_existing_check: bool = False,
        head_sha: str | None = None,
    ) -> bool:
        """Approve a PR and track metrics.
    
        Checks for existing active approvals first to avoid duplicate reviews.
        Two replicas handling the ``opened`` and ``labeled`` events of one new
        pull request can both pass that check, so after a successful approval
        the reviews are read again and every Stampbot approval of the same head
        except the oldest is dismissed. Approving twice and dismissing one is
        preferred to deferring, which could miss an approval when the other
        event never arrives.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            comment: Approval comment
            trigger_type: What triggered the approval (label, chatops)
            skip_existing_check: Whether to skip duplicate active approval detection
            head_sha: Current PR head SHA, used to distinguish stale approvals
    
        Returns:
            True if successful (or already approved)
        """
        with create_span(
            "webhook.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "approval.trigger_type": trigger_type,
            },
        ) as span:
            if not skip_existing_check:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Unknown state. A duplicate is recoverable; a missed
                    # approval is not, so approve and let the cleanup sort it out.
                    logger.warning(
                        "Could not read reviews for PR #%d in %s; approving without a check",
                        pr_number,
                        repo_full_name,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    reviews = []
                existing_approvals = self._active_approvals_for_head(reviews, head_sha)
                if existing_approvals:
                    logger.info(
                        "PR #%d in %s already has active approval for current head, skipping",
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "existing_review_ids": existing_approvals,
                        },
                    )
                    add_span_attributes(
                        span,
                        {
                            "approval.result": "already_approved",
                            "approval.existing_reviews": len(existing_approvals),
                        },
                    )
                    set_span_ok(span)
                    return True
    
            start_time = time.time()
    
            success = await run_in_threadpool(
                github_client.approve_pr,
                installation_id,
                repo_full_name,
                pr_number,
                comment,
            )
    
>           duration = time.time() ^ start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/webhook_handler.py:1004: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:32:47.833247Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:32:47.834286Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:32:47.834532Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:32:47.833247Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:32:47.834286Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:32:47.834532Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1096,7 +1096,7 @@
                         pr_number,
                         extra={"repo": repo_full_name, "pr_number": pr_number},
                     )
-                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
+                    pr_dismissal_duration_seconds.observe(time.time() ^ start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
                     return True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.........................................................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c27a3b47310>
mock_github_client = <MagicMock name='github_client' id='136509713712864'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
        """Test removing label when no bot reviews exist."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = []  # No reviews to dismiss
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:700: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:25:10.261599Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:25:10.262031Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:25:10.262126Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "unsupported operand type(s) for ^: 'float' and 'float'"}, "event": "Error dismissing approvals: unsupported operand type(s) for ^: 'float' and 'float'", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T03:25:10.262351Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:25:10.261599Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:25:10.262031Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:25:10.262126Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for ^: 'float' and 'float'", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T03:25:10.262351Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 417 passed, 3 deselected, 1 warning in 2.52s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1114,7 +1114,7 @@
                     )
                     success = success and result
 
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
                 status = "success" if success else "failure"
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......................................................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d35af13b310>
mock_github_client = <MagicMock name='github_client' id='137669545049600'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
        """Test removing autoapprove label dismisses approvals."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"},
            {"id": 222, "state": "APPROVED", "commit_id": "abc123def456"},
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:674: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:54:55.767481Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:54:55.767889Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:54:55.767979Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "unsupported operand type(s) for ^: 'float' and 'float'"}, "event": "Error dismissing approvals: unsupported operand type(s) for ^: 'float' and 'float'", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T03:54:55.768357Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:54:55.767481Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:54:55.767889Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:54:55.767979Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Error dismissing approvals: unsupported operand type(s) for ^: 'float' and 'float'", 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T03:54:55.768357Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 415 passed, 3 deselected, 1 warning in 2.52s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 3, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1126,7 +1126,7 @@
                 return success
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
....F
=================================== FAILURES ===================================
___ test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] ____

self = <stampbot.webhook_handler.WebhookHandler object at 0x7385eeaabf00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
>               reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )

stampbot/webhook_handler.py:1072: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:34: in run_in_threadpool
    return await anyio.to_thread.run_sync(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:65: in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2779: in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:1124: in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.find_bot_approval_reviews' id='127019093931888'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   RuntimeError: GitHub unavailable

/usr/lib/python3.14/unittest/mock.py:1245: RuntimeError

During handling of the above exception, another exception occurred:

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7385eeaabf00>
mock_github_client = <MagicMock name='github_client' id='127019093930544'>
failure = RuntimeError('GitHub unavailable')

    @pytest.mark.asyncio
    @pytest.mark.parametrize("failure", [RuntimeError("GitHub unavailable"), None])
    async def test_pr_duplicate_check_failure_keeps_approval_and_is_counted(
        webhook_handler, mock_github_client, failure
    ):
        """Test a failed duplicate scan keeps the approval and reaches the failure counter.
    
        The client reports a listing failure as None; an unexpected exception is
        handled the same way.
        """
        from stampbot.metrics import pr_dismissals_total
    
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [[], failure]
        failures = pr_dismissals_total.labels(trigger_type="duplicate", status="failure")
        before = failures._value.get()
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:166: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
stampbot/webhook_handler.py:1019: in _approve_pr
    await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7385eeaabf00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Nothing can be dismissed without the list, and the
                    # failure has to reach the metric operators watch.
                    raise RuntimeError("Stampbot reviews could not be listed")
                if keep_oldest:
                    # Duplicate cleanup only touches approvals proven to be of
                    # this head, and leaves the oldest of them in place.
                    review_ids = self._active_approvals_for_head(
                        reviews, head_sha, proven_only=True
                    )[1:]
                else:
                    review_ids = self._active_approvals_for_head(reviews, head_sha)
    
                add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
    
                if not review_ids:
                    logger.debug(
                        "No Stampbot approvals to dismiss on PR #%d",
                        pr_number,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
                    add_span_attributes(span, {"dismissal.result": "no_reviews"})
                    set_span_ok(span)
                    return True
    
                # Dismiss each review
                success = True
                for review_id in review_ids:
                    result = await run_in_threadpool(
                        github_client.dismiss_approval,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        review_id,
                        message,
                    )
                    success = success and result
    
                duration = time.time() - start_time
                pr_dismissal_duration_seconds.observe(duration)
    
                status = "success" if success else "failure"
                pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
    
                add_span_attributes(span, {"dismissal.result": status})
                set_span_ok(span)
    
                return success
    
            except Exception as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/webhook_handler.py:1129: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:26:16.070081Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:26:16.070511Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:26:16.070699Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:26:16.070081Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:26:16.070511Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:26:16.070699Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 364 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceBinaryOperator_Mul_Add, occurrence: 0, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -562,7 +562,7 @@
         self,
         reviews: list[BotReview],
         head_sha: str | None,
-        *,
+        +,
         proven_only: bool = False,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 565
E       +,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_Add, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1038,7 +1038,7 @@
         pr_number: int,
         message: str,
         trigger_type: str,
-        *,
+        +,
         head_sha: str | None = None,
         keep_oldest: bool = False,
     ) -> bool:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 1041
E       +,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_Sub, occurrence: 0, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -562,7 +562,7 @@
         self,
         reviews: list[BotReview],
         head_sha: str | None,
-        *,
+        -,
         proven_only: bool = False,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 565
E       -,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.98s
operator: core/ReplaceBinaryOperator_Mul_Sub, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1038,7 +1038,7 @@
         pr_number: int,
         message: str,
         trigger_type: str,
-        *,
+        -,
         head_sha: str | None = None,
         keep_oldest: bool = False,
     ) -> bool:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 1041
E       -,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_Div, occurrence: 0, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -562,7 +562,7 @@
         self,
         reviews: list[BotReview],
         head_sha: str | None,
-        *,
+        /,
         proven_only: bool = False,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceBinaryOperator_Mul_Div, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1038,7 +1038,7 @@
         pr_number: int,
         message: str,
         trigger_type: str,
-        *,
+        /,
         head_sha: str | None = None,
         keep_oldest: bool = False,
     ) -> bool:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceBinaryOperator_Mul_FloorDiv, occurrence: 0, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -562,7 +562,7 @@
         self,
         reviews: list[BotReview],
         head_sha: str | None,
-        *,
+        //,
         proven_only: bool = False,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 565
E       //,
E       ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_FloorDiv, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1038,7 +1038,7 @@
         pr_number: int,
         message: str,
         trigger_type: str,
-        *,
+        //,
         head_sha: str | None = None,
         keep_oldest: bool = False,
     ) -> bool:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 1041
E       //,
E       ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.02s
operator: core/ReplaceBinaryOperator_Mul_Mod, occurrence: 0, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -562,7 +562,7 @@
         self,
         reviews: list[BotReview],
         head_sha: str | None,
-        *,
+        %,
         proven_only: bool = False,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 565
E       %,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.97s
operator: core/ReplaceBinaryOperator_Mul_Mod, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1038,7 +1038,7 @@
         pr_number: int,
         message: str,
         trigger_type: str,
-        *,
+        %,
         head_sha: str | None = None,
         keep_oldest: bool = False,
     ) -> bool:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 1041
E       %,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.98s
operator: core/ReplaceBinaryOperator_Mul_Pow, occurrence: 0, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -562,7 +562,7 @@
         self,
         reviews: list[BotReview],
         head_sha: str | None,
-        *,
+        **,
         proven_only: bool = False,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 565
E       **,
E         ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_Pow, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1038,7 +1038,7 @@
         pr_number: int,
         message: str,
         trigger_type: str,
-        *,
+        **,
         head_sha: str | None = None,
         keep_oldest: bool = False,
     ) -> bool:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 1041
E       **,
E         ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.98s
operator: core/ReplaceBinaryOperator_Mul_RShift, occurrence: 0, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -562,7 +562,7 @@
         self,
         reviews: list[BotReview],
         head_sha: str | None,
-        *,
+        >>,
         proven_only: bool = False,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 565
E       >>,
E       ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_RShift, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1038,7 +1038,7 @@
         pr_number: int,
         message: str,
         trigger_type: str,
-        *,
+        >>,
         head_sha: str | None = None,
         keep_oldest: bool = False,
     ) -> bool:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 1041
E       >>,
E       ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_LShift, occurrence: 0, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -562,7 +562,7 @@
         self,
         reviews: list[BotReview],
         head_sha: str | None,
-        *,
+        <<,
         proven_only: bool = False,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 565
E       <<,
E       ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_LShift, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1038,7 +1038,7 @@
         pr_number: int,
         message: str,
         trigger_type: str,
-        *,
+        <<,
         head_sha: str | None = None,
         keep_oldest: bool = False,
     ) -> bool:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 1041
E       <<,
E       ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_BitOr, occurrence: 0, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -562,7 +562,7 @@
         self,
         reviews: list[BotReview],
         head_sha: str | None,
-        *,
+        |,
         proven_only: bool = False,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 565
E       |,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_BitOr, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1038,7 +1038,7 @@
         pr_number: int,
         message: str,
         trigger_type: str,
-        *,
+        |,
         head_sha: str | None = None,
         keep_oldest: bool = False,
     ) -> bool:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 1041
E       |,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_BitAnd, occurrence: 0, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -562,7 +562,7 @@
         self,
         reviews: list[BotReview],
         head_sha: str | None,
-        *,
+        &,
         proven_only: bool = False,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 565
E       &,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_BitAnd, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1038,7 +1038,7 @@
         pr_number: int,
         message: str,
         trigger_type: str,
-        *,
+        &,
         head_sha: str | None = None,
         keep_oldest: bool = False,
     ) -> bool:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 1041
E       &,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_BitXor, occurrence: 0, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -562,7 +562,7 @@
         self,
         reviews: list[BotReview],
         head_sha: str | None,
-        *,
+        ^,
         proven_only: bool = False,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 565
E       ^,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.99s
operator: core/ReplaceBinaryOperator_Mul_BitXor, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1038,7 +1038,7 @@
         pr_number: int,
         message: str,
         trigger_type: str,
-        *,
+        ^,
         head_sha: str | None = None,
         keep_oldest: bool = False,
     ) -> bool:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
E     File "/home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py", line 1041
E       ^,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.00s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -160,7 +160,7 @@
             )
 
             # Route to appropriate handler
-            if event_type == "pull_request":
+            if event_type != "pull_request":
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x70dcaf99b240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'error' == 'ok'
E         
E         - ok
E         + error

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:34:04.598158Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "Missing required fields in PR event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:34:04.598340Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:34:04.598796Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:34:04.598158Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:212 {'event': 'Missing required fields in PR event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:34:04.598340Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'error' == 'ok'
  
  - ok
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.15s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 1, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -162,7 +162,7 @@
             # Route to appropriate handler
             if event_type == "pull_request":
                 result = await self._handle_pull_request(payload)
-            elif event_type == "pull_request_review_comment":
+            elif event_type != "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
             elif event_type == "issue_comment":
                 # Issue comments can be on PRs too
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x7d889a4a3240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:59:16.439434Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:59:16.439963Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:59:16.439434Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.10s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 2, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -164,7 +164,7 @@
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
-            elif event_type == "issue_comment":
+            elif event_type != "issue_comment":
                 # Issue comments can be on PRs too
                 if "pull_request" in payload.get("issue", {}):
                     result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x723615b7b240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:32:54.360446Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:32:54.361003Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:32:54.360446Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.13s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 3, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -170,7 +170,7 @@
                     result = await self._handle_pr_comment(payload)
                 else:
                     result = {"status": "ignored", "message": "Not a PR comment"}
-            elif event_type == "ping":
+            elif event_type != "ping":
                 result = {"status": "ok", "message": "pong"}
             else:
                 result = {"status": "ignored", "message": f"Event type {event_type} not handled"}
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x732741887240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:59:27.299946Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:59:27.300379Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:59:27.299946Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.09s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 4, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -244,7 +244,7 @@
                     },
                 )
 
-                if action == "opened":
+                if action != "opened":
                     await run_in_threadpool(
                         github_client.create_pr_review_comment,
                         installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
..................F
=================================== FAILURES ===================================
_____ test_missing_repo_config_with_invalid_service_defaults_fails_closed ______

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x79199edd3150>
mock_github_client = <MagicMock name='github_client' id='133150942974304'>

    @pytest.mark.asyncio
    async def test_missing_repo_config_with_invalid_service_defaults_fails_closed(
        webhook_handler, mock_github_client
    ):
        """Test invalid service defaults block a PR when no repo config exists."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        service_defaults = MagicMock(spec=["approve_commands"])
        service_defaults.approve_commands = "approve"
    
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = service_defaults
            mock_settings.defaults = service_defaults
    
            result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result == {"status": "error", "message": "Invalid repository configuration"}
>       review_body = mock_github_client.create_pr_review_comment.call_args.args[3]
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AttributeError: 'NoneType' object has no attribute 'args'

tests/test_webhook_handler.py:1292: AttributeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:07:27.800411Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid service default configuration: approve_commands must be a list of strings"}, "event": "Invalid service default configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings", "logger": "stampbot.repo_policy", "level": "warning", "timestamp": "2026-11-04T04:07:27.800884Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "warning"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid service default configuration: approve_commands must be a list of strings"}, "event": "Invalid Stampbot configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:07:27.800980Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:07:27.800411Z'}
WARNING  stampbot.repo_policy:repo_policy.py:142 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid service default configuration: approve_commands must be a list of strings'}, 'event': 'Invalid service default configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings', 'logger': 'stampbot.repo_policy', 'level': 'warning', 'timestamp': '2026-11-04T04:07:27.800884Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:233 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid service default configuration: approve_commands must be a list of strings'}, 'event': 'Invalid Stampbot configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:07:27.800980Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_missing_repo_config_with_invalid_service_defaults_fails_closed - AttributeError: 'NoneType' object has no attribute 'args'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 450 passed, 3 deselected, 1 warning in 3.30s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 5, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -286,7 +286,7 @@
                 set_span_ok(span)
                 result = dict(NO_ACTION)
 
-            if action == "opened":
+            if action != "opened":
                 # This only produces an operator warning, so it runs after the
                 # review decision instead of delaying it.
                 await self._warn_missing_approval_labels(event)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x70f8bf937e70>
mock_github_client = <MagicMock name='github_client' id='124213688010128'>

    @pytest.mark.asyncio
    async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
        """Test missing approval label triggers label check after the review decision."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        mock_github_client.repo_has_label.return_value = False
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        # The label on the pull request exists by definition; only "stamp" is checked.
>       mock_github_client.repo_has_label.assert_called_once_with(12345, "octocat/hello-world", "stamp")

tests/test_webhook_handler.py:352: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.repo_has_label' id='124213689589376'>
args = (12345, 'octocat/hello-world', 'stamp'), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:34:50.822700Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:34:50.823098Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:34:50.823307Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:34:50.822700Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:34:50.823098Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:34:50.823307Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 382 passed, 3 deselected, 1 warning in 2.45s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 6, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -306,7 +306,7 @@
         if not config.auto_approve_on_label:
             return Decision.NONE, None
 
-        if event.action == "unlabeled":
+        if event.action != "unlabeled":
             if event.event_label in config.approval_labels:
                 return Decision.DISMISS, event.event_label
             return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77cf47f950f0>
mock_github_client = <MagicMock name='github_client' id='131732239308112'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:35:03.271986Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:35:03.272625Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:35:03.271986Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:35:03.272625Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.38s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 7, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -322,7 +322,7 @@
             # any duplicate afterwards.
             return Decision.APPROVE, label
 
-        if event.action == "labeled":
+        if event.action != "labeled":
             if event.event_label in config.approval_labels:
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a4de8f5e520>
mock_github_client = <MagicMock name='github_client' id='134475060248064'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
>       assert [r["status"] for r in results] == ["success", "success"]
E       AssertionError: assert ['success', 'ignored'] == ['success', 'success']
E         
E         At index 1 diff: 'ignored' != 'success'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E               'success',
E         -     'success',
E         +     'ignored',
E           ]

tests/test_webhook_handler.py:116: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:34:59.894968Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:34:59.895478Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:34:59.895675Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:34:59.896207Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:34:59.894968Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:34:59.895478Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:34:59.895675Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:34:59.896207Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: assert ['success', 'ignored'] == ['success', 'success']
  
  At index 1 diff: 'ignored' != 'success'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        'success',
  -     'success',
  +     'ignored',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 8, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -327,7 +327,7 @@
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
 
-        if event.action == "synchronize" and config.reapprove:
+        if event.action != "synchronize" and config.reapprove:
             return Decision.REFRESH, label
 
         return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a653d0f47d0>
mock_github_client = <MagicMock name='github_client' id='134575257727872'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:198: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:56:11.388335Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:56:11.388335Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.40s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 9, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -586,7 +586,7 @@
         def covers_head(review: BotReview) -> bool:
             commit = review.get("commit_id")
             if proven_only:
-                return bool(head_sha) and commit == head_sha
+                return bool(head_sha) and commit != head_sha
             return not head_sha or not commit or commit == head_sha
 
         return sorted(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73e66b3abdf0>
mock_github_client = <MagicMock name='github_client' id='127433500495888'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='127433500499584'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:41:35.775671Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:41:35.776101Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:41:35.776325Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:41:35.775671Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:41:35.776101Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:41:35.776325Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 10, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -587,7 +587,7 @@
             commit = review.get("commit_id")
             if proven_only:
                 return bool(head_sha) and commit == head_sha
-            return not head_sha or not commit or commit == head_sha
+            return not head_sha or not commit or commit != head_sha
 
         return sorted(
             review["id"]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x727a750ca520>
mock_github_client = <MagicMock name='github_client' id='125870296808960'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='125870296799552'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:17:19.512987Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:17:19.513489Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:17:19.513682Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:17:19.514194Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:17:19.514372Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:17:19.512987Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:17:19.513489Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:17:19.513682Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:17:19.514194Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:17:19.514372Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 11, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -592,7 +592,7 @@
         return sorted(
             review["id"]
             for review in reviews
-            if review["state"] == "APPROVED" and covers_head(review)
+            if review["state"] != "APPROVED" and covers_head(review)
         )
 
     def _has_current_active_approval(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x72d23c4ca520>
mock_github_client = <MagicMock name='github_client' id='126247301742080'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='126247301732672'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:05.004472Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:42:05.004956Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:05.005157Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:05.005681Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:05.005855Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:05.004472Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:42:05.004956Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:05.005157Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:05.005681Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:05.005855Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 12, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -704,7 +704,7 @@
             command = command_match.group(1).lower()
             add_span_attributes(span, {"chatops.command": command})
 
-            if command == "help":
+            if command != "help":
                 success = await self._post_help(
                     installation_id,
                     repo_full_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.................................................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a83d68672a0>
mock_github_client = <MagicMock name='github_client' id='134706680652064'>

    @pytest.mark.asyncio
    async def test_issue_comment_approve(webhook_handler, mock_github_client):
        """Test @stampbot approve command."""
        payload = load_fixture("issue_comment_approve")
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
>       assert "approved" in result["message"].lower()
E       AssertionError: assert 'approved' in 'help message posted'
E        +  where 'help message posted' = <built-in method lower of str object at 0x7a83d6c31270>()
E        +    where <built-in method lower of str object at 0x7a83d6c31270> = 'Help message posted'.lower

tests/test_webhook_handler.py:830: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:04:09.536796Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:04:09.537267Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:04:09.536796Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:04:09.537267Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'approved' in 'help message posted'
 +  where 'help message posted' = <built-in method lower of str object at 0x7a83d6c31270>()
 +    where <built-in method lower of str object at 0x7a83d6c31270> = 'Help message posted'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 425 passed, 3 deselected, 1 warning in 2.85s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -160,7 +160,7 @@
             )
 
             # Route to appropriate handler
-            if event_type == "pull_request":
+            if event_type < "pull_request":
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x79e665277240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'error' == 'ok'
E         
E         - ok
E         + error

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:08:00.696367Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "Missing required fields in PR event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:08:00.696490Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:08:00.697001Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:08:00.696367Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:212 {'event': 'Missing required fields in PR event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:08:00.696490Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'error' == 'ok'
  
  - ok
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.11s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 1, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -162,7 +162,7 @@
             # Route to appropriate handler
             if event_type == "pull_request":
                 result = await self._handle_pull_request(payload)
-            elif event_type == "pull_request_review_comment":
+            elif event_type < "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
             elif event_type == "issue_comment":
                 # Issue comments can be on PRs too
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x714ec0ba3240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:32:58.200732Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:32:58.201297Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:32:58.200732Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.12s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 2, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -164,7 +164,7 @@
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
-            elif event_type == "issue_comment":
+            elif event_type < "issue_comment":
                 # Issue comments can be on PRs too
                 if "pull_request" in payload.get("issue", {}):
                     result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c4301f99d90>
mock_github_client = <MagicMock name='github_client' id='136627255909680'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:46:58.187085Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:46:58.187085Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.50s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 3, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -170,7 +170,7 @@
                     result = await self._handle_pr_comment(payload)
                 else:
                     result = {"status": "ignored", "message": "Not a PR comment"}
-            elif event_type == "ping":
+            elif event_type < "ping":
                 result = {"status": "ok", "message": "pong"}
             else:
                 result = {"status": "ignored", "message": f"Event type {event_type} not handled"}
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x73118ab77240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:26:29.375766Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:26:29.376302Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:26:29.375766Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.15s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 4, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -244,7 +244,7 @@
                     },
                 )
 
-                if action == "opened":
+                if action < "opened":
                     await run_in_threadpool(
                         github_client.create_pr_review_comment,
                         installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
..................F
=================================== FAILURES ===================================
_____ test_missing_repo_config_with_invalid_service_defaults_fails_closed ______

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77de9a9ab150>
mock_github_client = <MagicMock name='github_client' id='131797957415264'>

    @pytest.mark.asyncio
    async def test_missing_repo_config_with_invalid_service_defaults_fails_closed(
        webhook_handler, mock_github_client
    ):
        """Test invalid service defaults block a PR when no repo config exists."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        service_defaults = MagicMock(spec=["approve_commands"])
        service_defaults.approve_commands = "approve"
    
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = service_defaults
            mock_settings.defaults = service_defaults
    
            result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result == {"status": "error", "message": "Invalid repository configuration"}
>       review_body = mock_github_client.create_pr_review_comment.call_args.args[3]
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AttributeError: 'NoneType' object has no attribute 'args'

tests/test_webhook_handler.py:1292: AttributeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:10:21.888771Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid service default configuration: approve_commands must be a list of strings"}, "event": "Invalid service default configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings", "logger": "stampbot.repo_policy", "level": "warning", "timestamp": "2026-11-04T04:10:21.889244Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "warning"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid service default configuration: approve_commands must be a list of strings"}, "event": "Invalid Stampbot configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:10:21.889337Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:10:21.888771Z'}
WARNING  stampbot.repo_policy:repo_policy.py:142 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid service default configuration: approve_commands must be a list of strings'}, 'event': 'Invalid service default configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings', 'logger': 'stampbot.repo_policy', 'level': 'warning', 'timestamp': '2026-11-04T04:10:21.889244Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:233 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid service default configuration: approve_commands must be a list of strings'}, 'event': 'Invalid Stampbot configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:10:21.889337Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_missing_repo_config_with_invalid_service_defaults_fails_closed - AttributeError: 'NoneType' object has no attribute 'args'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 450 passed, 3 deselected, 1 warning in 3.29s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 5, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -286,7 +286,7 @@
                 set_span_ok(span)
                 result = dict(NO_ACTION)
 
-            if action == "opened":
+            if action < "opened":
                 # This only produces an operator warning, so it runs after the
                 # review decision instead of delaying it.
                 await self._warn_missing_approval_labels(event)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x76a90d2b7e70>
mock_github_client = <MagicMock name='github_client' id='130468464690576'>

    @pytest.mark.asyncio
    async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
        """Test missing approval label triggers label check after the review decision."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        mock_github_client.repo_has_label.return_value = False
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        # The label on the pull request exists by definition; only "stamp" is checked.
>       mock_github_client.repo_has_label.assert_called_once_with(12345, "octocat/hello-world", "stamp")

tests/test_webhook_handler.py:352: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.repo_has_label' id='130468464172672'>
args = (12345, 'octocat/hello-world', 'stamp'), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:34:03.812971Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:34:03.813394Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:34:03.813586Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:34:03.812971Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:34:03.813394Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:34:03.813586Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 382 passed, 3 deselected, 1 warning in 2.45s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 6, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -306,7 +306,7 @@
         if not config.auto_approve_on_label:
             return Decision.NONE, None
 
-        if event.action == "unlabeled":
+        if event.action < "unlabeled":
             if event.event_label in config.approval_labels:
                 return Decision.DISMISS, event.event_label
             return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77f145a7d0f0>
mock_github_client = <MagicMock name='github_client' id='131878161274192'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:28:36.164352Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:28:36.165086Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:28:36.164352Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:28:36.165086Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 7, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -322,7 +322,7 @@
             # any duplicate afterwards.
             return Decision.APPROVE, label
 
-        if event.action == "labeled":
+        if event.action < "labeled":
             if event.event_label in config.approval_labels:
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x71bdd4d2e520>
mock_github_client = <MagicMock name='github_client' id='125060154146304'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
>       assert [r["status"] for r in results] == ["success", "success"]
E       AssertionError: assert ['success', 'ignored'] == ['success', 'success']
E         
E         At index 1 diff: 'ignored' != 'success'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E               'success',
E         -     'success',
E         +     'ignored',
E           ]

tests/test_webhook_handler.py:116: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:34:16.736039Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:34:16.736520Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:34:16.736729Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:34:16.737244Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:34:16.736039Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:34:16.736520Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:34:16.736729Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:34:16.737244Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: assert ['success', 'ignored'] == ['success', 'success']
  
  At index 1 diff: 'ignored' != 'success'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        'success',
  -     'success',
  +     'ignored',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.38s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 8, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -327,7 +327,7 @@
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
 
-        if event.action == "synchronize" and config.reapprove:
+        if event.action < "synchronize" and config.reapprove:
             return Decision.REFRESH, label
 
         return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x717a5dcd07d0>
mock_github_client = <MagicMock name='github_client' id='124770412376960'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:198: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:23:41.898157Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:23:41.898157Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 9, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -586,7 +586,7 @@
         def covers_head(review: BotReview) -> bool:
             commit = review.get("commit_id")
             if proven_only:
-                return bool(head_sha) and commit == head_sha
+                return bool(head_sha) and commit < head_sha
             return not head_sha or not commit or commit == head_sha
 
         return sorted(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c845c483df0>
mock_github_client = <MagicMock name='github_client' id='136907947560976'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='136907947564672'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:33:53.189377Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:33:53.189794Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:33:53.189978Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:33:53.189377Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:33:53.189794Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:33:53.189978Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 10, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -587,7 +587,7 @@
             commit = review.get("commit_id")
             if proven_only:
                 return bool(head_sha) and commit == head_sha
-            return not head_sha or not commit or commit == head_sha
+            return not head_sha or not commit or commit < head_sha
 
         return sorted(
             review["id"]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c3c92abe520>
mock_github_client = <MagicMock name='github_client' id='136599622073856'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='136599622064448'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:26.872927Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:09:26.873402Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:26.873600Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:26.874106Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:26.874313Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:26.872927Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:09:26.873402Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:26.873600Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:26.874106Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:26.874313Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 11, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -592,7 +592,7 @@
         return sorted(
             review["id"]
             for review in reviews
-            if review["state"] == "APPROVED" and covers_head(review)
+            if review["state"] < "APPROVED" and covers_head(review)
         )
 
     def _has_current_active_approval(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7581df52a520>
mock_github_client = <MagicMock name='github_client' id='129200680107520'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='129200680098112'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:40:16.820277Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:40:16.820766Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:40:16.820968Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:40:16.821485Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:40:16.821658Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:40:16.820277Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:40:16.820766Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:40:16.820968Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:40:16.821485Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:40:16.821658Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 12, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -704,7 +704,7 @@
             command = command_match.group(1).lower()
             add_span_attributes(span, {"chatops.command": command})
 
-            if command == "help":
+            if command < "help":
                 success = await self._post_help(
                     installation_id,
                     repo_full_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.................................................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7b86239af2a0>
mock_github_client = <MagicMock name='github_client' id='135816076370208'>

    @pytest.mark.asyncio
    async def test_issue_comment_approve(webhook_handler, mock_github_client):
        """Test @stampbot approve command."""
        payload = load_fixture("issue_comment_approve")
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
>       assert "approved" in result["message"].lower()
E       AssertionError: assert 'approved' in 'help message posted'
E        +  where 'help message posted' = <built-in method lower of str object at 0x7b8623d60df0>()
E        +    where <built-in method lower of str object at 0x7b8623d60df0> = 'Help message posted'.lower

tests/test_webhook_handler.py:830: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:29:58.884275Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:29:58.884752Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:29:58.884275Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:29:58.884752Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'approved' in 'help message posted'
 +  where 'help message posted' = <built-in method lower of str object at 0x7b8623d60df0>()
 +    where <built-in method lower of str object at 0x7b8623d60df0> = 'Help message posted'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 425 passed, 3 deselected, 1 warning in 2.86s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -160,7 +160,7 @@
             )
 
             # Route to appropriate handler
-            if event_type == "pull_request":
+            if event_type <= "pull_request":
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x72c45839b240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'error' == 'ok'
E         
E         - ok
E         + error

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:20:54.691421Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "Missing required fields in PR event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:20:54.691547Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:20:54.692040Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:20:54.691421Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:212 {'event': 'Missing required fields in PR event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:20:54.691547Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'error' == 'ok'
  
  - ok
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.14s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 1, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -162,7 +162,7 @@
             # Route to appropriate handler
             if event_type == "pull_request":
                 result = await self._handle_pull_request(payload)
-            elif event_type == "pull_request_review_comment":
+            elif event_type <= "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
             elif event_type == "issue_comment":
                 # Issue comments can be on PRs too
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x7d6c5089f240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:48:20.391085Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:48:20.391682Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:48:20.391085Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.19s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 2, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -164,7 +164,7 @@
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
-            elif event_type == "issue_comment":
+            elif event_type <= "issue_comment":
                 # Issue comments can be on PRs too
                 if "pull_request" in payload.get("issue", {}):
                     result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 3, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -170,7 +170,7 @@
                     result = await self._handle_pr_comment(payload)
                 else:
                     result = {"status": "ignored", "message": "Not a PR comment"}
-            elif event_type == "ping":
+            elif event_type <= "ping":
                 result = {"status": "ok", "message": "pong"}
             else:
                 result = {"status": "ignored", "message": f"Event type {event_type} not handled"}
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 4, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -244,7 +244,7 @@
                     },
                 )
 
-                if action == "opened":
+                if action <= "opened":
                     await run_in_threadpool(
                         github_client.create_pr_review_comment,
                         installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
......................F
=================================== FAILURES ===================================
_______________ test_invalid_repo_config_no_review_on_non_opened _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74eeb04ed5c0>
mock_github_client = <MagicMock name='github_client' id='128568507202736'>

    @pytest.mark.asyncio
    async def test_invalid_repo_config_no_review_on_non_opened(webhook_handler, mock_github_client):
        """Test invalid config skips review comment for non-opened events."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        payload["action"] = "labeled"
        mock_github_client.get_repo_file.return_value = 'chatops_required_permission = "invalid"'
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "error"
>       mock_github_client.create_pr_review_comment.assert_not_called()

tests/test_webhook_handler.py:1364: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.create_pr_review_comment' id='128568507199376'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'create_pr_review_comment' to not have been called. Called 1 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Stampbot configuration error:\n\nInvalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin\n\nPlease fix the configuration to re-enable automation.')].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:52:43.608498Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin"}, "event": "Invalid Stampbot configuration for octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:52:43.608975Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:52:43.608498Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:233 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}, 'event': 'Invalid Stampbot configuration for octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:52:43.608975Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_invalid_repo_config_no_review_on_non_opened - AssertionError: Expected 'create_pr_review_comment' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Stampbot configuration error:\n\nInvalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin\n\nPlease fix the configuration to re-enable automation.')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 454 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 5, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -286,7 +286,7 @@
                 set_span_ok(span)
                 result = dict(NO_ACTION)
 
-            if action == "opened":
+            if action <= "opened":
                 # This only produces an operator warning, so it runs after the
                 # review decision instead of delaying it.
                 await self._warn_missing_approval_labels(event)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 6, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -306,7 +306,7 @@
         if not config.auto_approve_on_label:
             return Decision.NONE, None
 
-        if event.action == "unlabeled":
+        if event.action <= "unlabeled":
             if event.event_label in config.approval_labels:
                 return Decision.DISMISS, event.event_label
             return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x751297dd50f0>
mock_github_client = <MagicMock name='github_client' id='128722807248208'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:02:12.068928Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:02:12.069544Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:02:12.068928Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:02:12.069544Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.35s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 7, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -322,7 +322,7 @@
             # any duplicate afterwards.
             return Decision.APPROVE, label
 
-        if event.action == "labeled":
+        if event.action <= "labeled":
             if event.event_label in config.approval_labels:
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.41s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 8, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -327,7 +327,7 @@
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
 
-        if event.action == "synchronize" and config.reapprove:
+        if event.action <= "synchronize" and config.reapprove:
             return Decision.REFRESH, label
 
         return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 9, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -586,7 +586,7 @@
         def covers_head(review: BotReview) -> bool:
             commit = review.get("commit_id")
             if proven_only:
-                return bool(head_sha) and commit == head_sha
+                return bool(head_sha) and commit <= head_sha
             return not head_sha or not commit or commit == head_sha
 
         return sorted(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
................F
=================================== FAILURES ===================================
_____ test_active_approvals_for_head_proven_only[reviews1-head-expected1] ______

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74a8aa18f250>
reviews = [{'id': 1, 'state': 'APPROVED', 'commit_id': None}], head_sha = 'head'
expected = []

    @pytest.mark.parametrize(
        ("reviews", "head_sha", "expected"),
        [
            ([{"id": 1, "state": "APPROVED", "commit_id": "head"}], None, []),
            ([{"id": 1, "state": "APPROVED", "commit_id": None}], "head", []),
            (
                [
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                    {"id": 1, "state": "APPROVED", "commit_id": None},
                    {"id": 3, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2, 3],
            ),
        ],
    )
    def test_active_approvals_for_head_proven_only(webhook_handler, reviews, head_sha, expected):
        """Test duplicate cleanup never selects an approval on a guess."""
        assert (
>           webhook_handler._active_approvals_for_head(reviews, head_sha, proven_only=True) == expected
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        )

tests/test_webhook_handler.py:278: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:592: in _active_approvals_for_head
    return sorted(
stampbot/webhook_handler.py:595: in <genexpr>
    if review["state"] == "APPROVED" and covers_head(review)
                                         ^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

review = {'id': 1, 'state': 'APPROVED', 'commit_id': None}

    def covers_head(review: BotReview) -> bool:
        commit = review.get("commit_id")
        if proven_only:
>           return bool(head_sha) and commit <= head_sha
                                      ^^^^^^^^^^^^^^^^^^
E           TypeError: '<=' not supported between instances of 'NoneType' and 'str'

stampbot/webhook_handler.py:589: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_active_approvals_for_head_proven_only[reviews1-head-expected1] - TypeError: '<=' not supported between instances of 'NoneType' and 'str'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 376 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 10, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -587,7 +587,7 @@
             commit = review.get("commit_id")
             if proven_only:
                 return bool(head_sha) and commit == head_sha
-            return not head_sha or not commit or commit == head_sha
+            return not head_sha or not commit or commit <= head_sha
 
         return sorted(
             review["id"]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 11, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -592,7 +592,7 @@
         return sorted(
             review["id"]
             for review in reviews
-            if review["state"] == "APPROVED" and covers_head(review)
+            if review["state"] <= "APPROVED" and covers_head(review)
         )
 
     def _has_current_active_approval(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 12, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -704,7 +704,7 @@
             command = command_match.group(1).lower()
             add_span_attributes(span, {"chatops.command": command})
 
-            if command == "help":
+            if command <= "help":
                 success = await self._post_help(
                     installation_id,
                     repo_full_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.................................................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f64e48432a0>
mock_github_client = <MagicMock name='github_client' id='140071329849632'>

    @pytest.mark.asyncio
    async def test_issue_comment_approve(webhook_handler, mock_github_client):
        """Test @stampbot approve command."""
        payload = load_fixture("issue_comment_approve")
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
>       assert "approved" in result["message"].lower()
E       AssertionError: assert 'approved' in 'help message posted'
E        +  where 'help message posted' = <built-in method lower of str object at 0x7f64e4c55470>()
E        +    where <built-in method lower of str object at 0x7f64e4c55470> = 'Help message posted'.lower

tests/test_webhook_handler.py:830: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:50:35.396554Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:50:35.397010Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:50:35.396554Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:50:35.397010Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'approved' in 'help message posted'
 +  where 'help message posted' = <built-in method lower of str object at 0x7f64e4c55470>()
 +    where <built-in method lower of str object at 0x7f64e4c55470> = 'Help message posted'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 425 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -160,7 +160,7 @@
             )
 
             # Route to appropriate handler
-            if event_type == "pull_request":
+            if event_type > "pull_request":
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.......................................................................F [ 75%]

=================================== FAILURES ===================================
______________________________ test_unknown_event ______________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7e61f24b3250>

    @pytest.mark.asyncio
    async def test_unknown_event(webhook_handler):
        """Test handling unknown event type."""
        payload = {}
        result = await webhook_handler.handle_event("unknown", payload)
>       assert result["status"] == "ignored"
E       AssertionError: assert 'error' == 'ignored'
E         
E         - ignored
E         + error

tests/test_webhook_handler.py:62: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "unknown", "action": ""}, "event": "Received unknown event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:45:43.344735Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "Missing required fields in PR event", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:45:43.344838Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'unknown', 'action': ''}, 'event': 'Received unknown event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:45:43.344735Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:212 {'event': 'Missing required fields in PR event', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:45:43.344838Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_unknown_event - AssertionError: assert 'error' == 'ignored'
  
  - ignored
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 359 passed, 3 deselected, 1 warning in 2.46s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 1, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -162,7 +162,7 @@
             # Route to appropriate handler
             if event_type == "pull_request":
                 result = await self._handle_pull_request(payload)
-            elif event_type == "pull_request_review_comment":
+            elif event_type > "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
             elif event_type == "issue_comment":
                 # Issue comments can be on PRs too
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
..............................F
=================================== FAILURES ===================================
____________________ test_pull_request_review_comment_event ____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x71959fae6c10>
mock_github_client = <MagicMock name='github_client' id='124887437701008'>

    @pytest.mark.asyncio
    async def test_pull_request_review_comment_event(webhook_handler, mock_github_client):
        """Test handling pull_request_review_comment event type."""
        payload = {
            "action": "created",
            "pull_request": {
                "number": 42,
            },
            "comment": {
                "body": "@stampbot approve",
                "user": {"login": "testuser"},
            },
            "repository": {
                "full_name": "owner/repo",
            },
            "installation": {"id": 12345},
        }
    
        result = await webhook_handler.handle_event("pull_request_review_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:1487: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request_review_comment", "action": "created"}, "event": "Received pull_request_review_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:41:36.353573Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request_review_comment', 'action': 'created'}, 'event': 'Received pull_request_review_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:41:36.353573Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pull_request_review_comment_event - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 462 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 2, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -164,7 +164,7 @@
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
-            elif event_type == "issue_comment":
+            elif event_type > "issue_comment":
                 # Issue comments can be on PRs too
                 if "pull_request" in payload.get("issue", {}):
                     result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x7358adb77240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:14:26.752983Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:14:26.753536Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:14:26.752983Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.12s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 3, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -170,7 +170,7 @@
                     result = await self._handle_pr_comment(payload)
                 else:
                     result = {"status": "ignored", "message": "Not a PR comment"}
-            elif event_type == "ping":
+            elif event_type > "ping":
                 result = {"status": "ok", "message": "pong"}
             else:
                 result = {"status": "ignored", "message": f"Event type {event_type} not handled"}
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x763c9a7a7240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:25:03.709923Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:25:03.710468Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:25:03.709923Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.12s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 4, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -244,7 +244,7 @@
                     },
                 )
 
-                if action == "opened":
+                if action > "opened":
                     await run_in_threadpool(
                         github_client.create_pr_review_comment,
                         installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
..................F
=================================== FAILURES ===================================
_____ test_missing_repo_config_with_invalid_service_defaults_fails_closed ______

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73fef51ab770>
mock_github_client = <MagicMock name='github_client' id='127538871865696'>

    @pytest.mark.asyncio
    async def test_missing_repo_config_with_invalid_service_defaults_fails_closed(
        webhook_handler, mock_github_client
    ):
        """Test invalid service defaults block a PR when no repo config exists."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        service_defaults = MagicMock(spec=["approve_commands"])
        service_defaults.approve_commands = "approve"
    
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = service_defaults
            mock_settings.defaults = service_defaults
    
            result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result == {"status": "error", "message": "Invalid repository configuration"}
>       review_body = mock_github_client.create_pr_review_comment.call_args.args[3]
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AttributeError: 'NoneType' object has no attribute 'args'

tests/test_webhook_handler.py:1292: AttributeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:28:52.768331Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid service default configuration: approve_commands must be a list of strings"}, "event": "Invalid service default configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings", "logger": "stampbot.repo_policy", "level": "warning", "timestamp": "2026-11-04T04:28:52.768798Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "warning"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid service default configuration: approve_commands must be a list of strings"}, "event": "Invalid Stampbot configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:28:52.768889Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:28:52.768331Z'}
WARNING  stampbot.repo_policy:repo_policy.py:142 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid service default configuration: approve_commands must be a list of strings'}, 'event': 'Invalid service default configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings', 'logger': 'stampbot.repo_policy', 'level': 'warning', 'timestamp': '2026-11-04T04:28:52.768798Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:233 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid service default configuration: approve_commands must be a list of strings'}, 'event': 'Invalid Stampbot configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:28:52.768889Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_missing_repo_config_with_invalid_service_defaults_fails_closed - AttributeError: 'NoneType' object has no attribute 'args'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 450 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 5, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -286,7 +286,7 @@
                 set_span_ok(span)
                 result = dict(NO_ACTION)
 
-            if action == "opened":
+            if action > "opened":
                 # This only produces an operator warning, so it runs after the
                 # review decision instead of delaying it.
                 await self._warn_missing_approval_labels(event)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d64a713be70>
mock_github_client = <MagicMock name='github_client' id='137871272969616'>

    @pytest.mark.asyncio
    async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
        """Test missing approval label triggers label check after the review decision."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        mock_github_client.repo_has_label.return_value = False
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        # The label on the pull request exists by definition; only "stamp" is checked.
>       mock_github_client.repo_has_label.assert_called_once_with(12345, "octocat/hello-world", "stamp")

tests/test_webhook_handler.py:352: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.repo_has_label' id='137871274548864'>
args = (12345, 'octocat/hello-world', 'stamp'), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:41:42.495863Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:41:42.496299Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:41:42.496486Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:41:42.495863Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:41:42.496299Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:41:42.496486Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 382 passed, 3 deselected, 1 warning in 2.46s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 6, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -306,7 +306,7 @@
         if not config.auto_approve_on_label:
             return Decision.NONE, None
 
-        if event.action == "unlabeled":
+        if event.action > "unlabeled":
             if event.event_label in config.approval_labels:
                 return Decision.DISMISS, event.event_label
             return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x766f9e5d6030>
mock_github_client = <MagicMock name='github_client' id='130221792478608'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "error"
E       AssertionError: assert 'ignored' == 'error'
E         
E         - error
E         + ignored

tests/test_webhook_handler.py:210: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:12:57.058690Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:12:57.059089Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:12:57.058690Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:12:57.059089Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - AssertionError: assert 'ignored' == 'error'
  
  - error
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.39s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 7, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -322,7 +322,7 @@
             # any duplicate afterwards.
             return Decision.APPROVE, label
 
-        if event.action == "labeled":
+        if event.action > "labeled":
             if event.event_label in config.approval_labels:
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7641e890e520>
mock_github_client = <MagicMock name='github_client' id='130025467739648'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
>       assert [r["status"] for r in results] == ["success", "success"]
E       AssertionError: assert ['success', 'ignored'] == ['success', 'success']
E         
E         At index 1 diff: 'ignored' != 'success'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E               'success',
E         -     'success',
E         +     'ignored',
E           ]

tests/test_webhook_handler.py:116: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:58:40.095965Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:58:40.096456Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:58:40.096674Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:58:40.097188Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:58:40.095965Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:58:40.096456Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:58:40.096674Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:58:40.097188Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: assert ['success', 'ignored'] == ['success', 'success']
  
  At index 1 diff: 'ignored' != 'success'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        'success',
  -     'success',
  +     'ignored',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.36s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 8, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -327,7 +327,7 @@
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
 
-        if event.action == "synchronize" and config.reapprove:
+        if event.action > "synchronize" and config.reapprove:
             return Decision.REFRESH, label
 
         return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x734197c587d0>
mock_github_client = <MagicMock name='github_client' id='126725652165504'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:198: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:17:09.867026Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:17:09.867026Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.40s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 9, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -586,7 +586,7 @@
         def covers_head(review: BotReview) -> bool:
             commit = review.get("commit_id")
             if proven_only:
-                return bool(head_sha) and commit == head_sha
+                return bool(head_sha) and commit > head_sha
             return not head_sha or not commit or commit == head_sha
 
         return sorted(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7afae99abdf0>
mock_github_client = <MagicMock name='github_client' id='135218101522448'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='135218101526144'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:08:49.622907Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:08:49.623333Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:08:49.623525Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:08:49.622907Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:08:49.623333Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:08:49.623525Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 10, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -587,7 +587,7 @@
             commit = review.get("commit_id")
             if proven_only:
                 return bool(head_sha) and commit == head_sha
-            return not head_sha or not commit or commit == head_sha
+            return not head_sha or not commit or commit > head_sha
 
         return sorted(
             review["id"]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a195b79e520>
mock_github_client = <MagicMock name='github_client' id='134249422323200'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='134249422313792'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:37:16.730151Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:37:16.730647Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:37:16.730838Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:37:16.731343Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:37:16.731517Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:37:16.730151Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:37:16.730647Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:37:16.730838Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:37:16.731343Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:37:16.731517Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 11, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -592,7 +592,7 @@
         return sorted(
             review["id"]
             for review in reviews
-            if review["state"] == "APPROVED" and covers_head(review)
+            if review["state"] > "APPROVED" and covers_head(review)
         )
 
     def _has_current_active_approval(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x745c5ff12520>
mock_github_client = <MagicMock name='github_client' id='127940185683824'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='127940185676096'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:39:25.462186Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:39:25.462683Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:39:25.462884Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:39:25.463410Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:39:25.463585Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:39:25.462186Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:39:25.462683Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:39:25.462884Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:39:25.463410Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:39:25.463585Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 12, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -704,7 +704,7 @@
             command = command_match.group(1).lower()
             add_span_attributes(span, {"chatops.command": command})
 
-            if command == "help":
+            if command > "help":
                 success = await self._post_help(
                     installation_id,
                     repo_full_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................................................................F
=================================== FAILURES ===================================
_________________________ test_issue_comment_unapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x72f1402bee40>
mock_github_client = <MagicMock name='github_client' id='126380508649120'>

    @pytest.mark.asyncio
    async def test_issue_comment_unapprove(webhook_handler, mock_github_client):
        """Test @stampbot unapprove command."""
        payload = load_fixture("issue_comment_unapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"}
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
>       assert "dismissed" in result["message"].lower()
E       AssertionError: assert 'dismissed' in 'help message posted'
E        +  where 'help message posted' = <built-in method lower of str object at 0x72f14064d7b0>()
E        +    where <built-in method lower of str object at 0x72f14064d7b0> = 'Help message posted'.lower

tests/test_webhook_handler.py:922: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:36:21.785270Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:36:21.785696Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:36:21.785270Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:36:21.785696Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_unapprove - AssertionError: assert 'dismissed' in 'help message posted'
 +  where 'help message posted' = <built-in method lower of str object at 0x72f14064d7b0>()
 +    where <built-in method lower of str object at 0x72f14064d7b0> = 'Help message posted'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 430 passed, 3 deselected, 1 warning in 2.87s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -160,7 +160,7 @@
             )
 
             # Route to appropriate handler
-            if event_type == "pull_request":
+            if event_type >= "pull_request":
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.......................................................................F [ 75%]

=================================== FAILURES ===================================
______________________________ test_unknown_event ______________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7169d7c47250>

    @pytest.mark.asyncio
    async def test_unknown_event(webhook_handler):
        """Test handling unknown event type."""
        payload = {}
        result = await webhook_handler.handle_event("unknown", payload)
>       assert result["status"] == "ignored"
E       AssertionError: assert 'error' == 'ignored'
E         
E         - ignored
E         + error

tests/test_webhook_handler.py:62: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "unknown", "action": ""}, "event": "Received unknown event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:26:18.247901Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "Missing required fields in PR event", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:26:18.248000Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'unknown', 'action': ''}, 'event': 'Received unknown event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:26:18.247901Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:212 {'event': 'Missing required fields in PR event', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:26:18.248000Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_unknown_event - AssertionError: assert 'error' == 'ignored'
  
  - ignored
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 359 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 1, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -162,7 +162,7 @@
             # Route to appropriate handler
             if event_type == "pull_request":
                 result = await self._handle_pull_request(payload)
-            elif event_type == "pull_request_review_comment":
+            elif event_type >= "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
             elif event_type == "issue_comment":
                 # Issue comments can be on PRs too
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 2, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -164,7 +164,7 @@
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
-            elif event_type == "issue_comment":
+            elif event_type >= "issue_comment":
                 # Issue comments can be on PRs too
                 if "pull_request" in payload.get("issue", {}):
                     result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x7a067fd87240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:15:25.203585Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:15:25.204112Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:15:25.203585Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.12s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 3, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -170,7 +170,7 @@
                     result = await self._handle_pr_comment(payload)
                 else:
                     result = {"status": "ignored", "message": "Not a PR comment"}
-            elif event_type == "ping":
+            elif event_type >= "ping":
                 result = {"status": "ok", "message": "pong"}
             else:
                 result = {"status": "ignored", "message": f"Event type {event_type} not handled"}
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.......................................................................F [ 75%]

=================================== FAILURES ===================================
______________________________ test_unknown_event ______________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7308b4743250>

    @pytest.mark.asyncio
    async def test_unknown_event(webhook_handler):
        """Test handling unknown event type."""
        payload = {}
        result = await webhook_handler.handle_event("unknown", payload)
>       assert result["status"] == "ignored"
E       AssertionError: assert 'ok' == 'ignored'
E         
E         - ignored
E         + ok

tests/test_webhook_handler.py:62: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "unknown", "action": ""}, "event": "Received unknown event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:54:17.708992Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'unknown', 'action': ''}, 'event': 'Received unknown event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:54:17.708992Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_unknown_event - AssertionError: assert 'ok' == 'ignored'
  
  - ignored
  + ok
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 359 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 4, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -244,7 +244,7 @@
                     },
                 )
 
-                if action == "opened":
+                if action >= "opened":
                     await run_in_threadpool(
                         github_client.create_pr_review_comment,
                         installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 5, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -286,7 +286,7 @@
                 set_span_ok(span)
                 result = dict(NO_ACTION)
 
-            if action == "opened":
+            if action >= "opened":
                 # This only produces an operator warning, so it runs after the
                 # review decision instead of delaying it.
                 await self._warn_missing_approval_labels(event)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 6, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -306,7 +306,7 @@
         if not config.auto_approve_on_label:
             return Decision.NONE, None
 
-        if event.action == "unlabeled":
+        if event.action >= "unlabeled":
             if event.event_label in config.approval_labels:
                 return Decision.DISMISS, event.event_label
             return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 7, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -322,7 +322,7 @@
             # any duplicate afterwards.
             return Decision.APPROVE, label
 
-        if event.action == "labeled":
+        if event.action >= "labeled":
             if event.event_label in config.approval_labels:
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.....................................F
=================================== FAILURES ===================================
____________________ test_pr_synchronize_reapprove_disabled ____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x761d26611860>
mock_github_client = <MagicMock name='github_client' id='129867590475392'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_reapprove_disabled(webhook_handler, mock_github_client):
        """Test new commits do not re-approve by default."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "ignored"
        assert "no action" in result["message"].lower()
>       mock_github_client.find_bot_approval_reviews.assert_not_called()

tests/test_webhook_handler.py:531: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.find_bot_approval_reviews' id='129867590470016'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'find_bot_approval_reviews' to not have been called. Called 1 times.
E           Calls: [call(12345, 'octocat/hello-world', 42)].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:19:42.953625Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:19:42.954030Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:19:42.953625Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:19:42.954030Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_reapprove_disabled - AssertionError: Expected 'find_bot_approval_reviews' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42)].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 397 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 8, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -327,7 +327,7 @@
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
 
-        if event.action == "synchronize" and config.reapprove:
+        if event.action >= "synchronize" and config.reapprove:
             return Decision.REFRESH, label
 
         return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 9, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -586,7 +586,7 @@
         def covers_head(review: BotReview) -> bool:
             commit = review.get("commit_id")
             if proven_only:
-                return bool(head_sha) and commit == head_sha
+                return bool(head_sha) and commit >= head_sha
             return not head_sha or not commit or commit == head_sha
 
         return sorted(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d604c2afdf0>
mock_github_client = <MagicMock name='github_client' id='137852569997328'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='137852570001024'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 100, 'Duplicate Stampbot approval'),
E            call(12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:43:26.348714Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:43:26.349112Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:43:26.349331Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:43:26.348714Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:43:26.349112Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:43:26.349331Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 100, 'Duplicate Stampbot approval'),
 call(12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 10, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -587,7 +587,7 @@
             commit = review.get("commit_id")
             if proven_only:
                 return bool(head_sha) and commit == head_sha
-            return not head_sha or not commit or commit == head_sha
+            return not head_sha or not commit or commit >= head_sha
 
         return sorted(
             review["id"]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
............F
=================================== FAILURES ===================================
___________ test_active_approvals_for_head[reviews3-head-expected3] ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7b18cccde690>
reviews = [{'id': 1, 'state': 'APPROVED', 'commit_id': 'old'}, {'id': 2, 'state': 'APPROVED', 'commit_id': 'head'}]
head_sha = 'head', expected = [2]

    @pytest.mark.parametrize(
        ("reviews", "head_sha", "expected"),
        [
            ([], "head", []),
            ([{"id": 1, "state": "APPROVED", "commit_id": "head"}], "head", [1]),
            (
                [
                    {"id": 3, "state": "APPROVED", "commit_id": "head"},
                    {"id": 1, "state": "APPROVED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [1, 2, 3],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": "old"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": None},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                None,
                [1, 2],
            ),
            (
                [
                    {"id": 1, "state": "DISMISSED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
        ],
    )
    def test_active_approvals_for_head(webhook_handler, reviews, head_sha, expected):
        """Test which approvals count as covering the head, oldest first."""
>       assert webhook_handler._active_approvals_for_head(reviews, head_sha) == expected
E       assert [1, 2] == [2]
E         
E         At index 0 diff: 1 != 2
E         Left contains one more item: 2
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         +     1,
E               2,
E           ]

tests/test_webhook_handler.py:256: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_active_approvals_for_head[reviews3-head-expected3] - assert [1, 2] == [2]
  
  At index 0 diff: 1 != 2
  Left contains one more item: 2
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  +     1,
        2,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 372 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 11, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -592,7 +592,7 @@
         return sorted(
             review["id"]
             for review in reviews
-            if review["state"] == "APPROVED" and covers_head(review)
+            if review["state"] >= "APPROVED" and covers_head(review)
         )
 
     def _has_current_active_approval(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7e92481a7df0>
mock_github_client = <MagicMock name='github_client' id='139166761766928'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='139166761770624'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 100, 'Duplicate Stampbot approval'),
E            call(12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:43:55.617878Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:43:55.618293Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:43:55.618480Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:43:55.617878Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:43:55.618293Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:43:55.618480Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 100, 'Duplicate Stampbot approval'),
 call(12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 12, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -704,7 +704,7 @@
             command = command_match.group(1).lower()
             add_span_attributes(span, {"chatops.command": command})
 
-            if command == "help":
+            if command >= "help":
                 success = await self._post_help(
                     installation_id,
                     repo_full_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................................................................F
=================================== FAILURES ===================================
_________________________ test_issue_comment_unapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7bd578b3acf0>
mock_github_client = <MagicMock name='github_client' id='136156803208864'>

    @pytest.mark.asyncio
    async def test_issue_comment_unapprove(webhook_handler, mock_github_client):
        """Test @stampbot unapprove command."""
        payload = load_fixture("issue_comment_unapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"}
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
>       assert "dismissed" in result["message"].lower()
E       AssertionError: assert 'dismissed' in 'help message posted'
E        +  where 'help message posted' = <built-in method lower of str object at 0x7bd578f41730>()
E        +    where <built-in method lower of str object at 0x7bd578f41730> = 'Help message posted'.lower

tests/test_webhook_handler.py:922: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:23:34.144278Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:23:34.144693Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:23:34.144278Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:23:34.144693Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_unapprove - AssertionError: assert 'dismissed' in 'help message posted'
 +  where 'help message posted' = <built-in method lower of str object at 0x7bd578f41730>()
 +    where <built-in method lower of str object at 0x7bd578f41730> = 'Help message posted'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 430 passed, 3 deselected, 1 warning in 2.86s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -160,7 +160,7 @@
             )
 
             # Route to appropriate handler
-            if event_type == "pull_request":
+            if event_type is "pull_request":
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:163
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:163: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if event_type is "pull_request":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.33s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 1, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -162,7 +162,7 @@
             # Route to appropriate handler
             if event_type == "pull_request":
                 result = await self._handle_pull_request(payload)
-            elif event_type == "pull_request_review_comment":
+            elif event_type is "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
             elif event_type == "issue_comment":
                 # Issue comments can be on PRs too
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:165
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:165: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    elif event_type is "pull_request_review_comment":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.39s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 2, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -164,7 +164,7 @@
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
-            elif event_type == "issue_comment":
+            elif event_type is "issue_comment":
                 # Issue comments can be on PRs too
                 if "pull_request" in payload.get("issue", {}):
                     result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:167
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:167: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    elif event_type is "issue_comment":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.41s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 3, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -170,7 +170,7 @@
                     result = await self._handle_pr_comment(payload)
                 else:
                     result = {"status": "ignored", "message": "Not a PR comment"}
-            elif event_type == "ping":
+            elif event_type is "ping":
                 result = {"status": "ok", "message": "pong"}
             else:
                 result = {"status": "ignored", "message": f"Event type {event_type} not handled"}
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x78f94fe77240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:20:28.910237Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:20:28.910770Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:20:28.910237Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:173
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:173: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    elif event_type is "ping":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 2 warnings in 2.13s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 4, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -244,7 +244,7 @@
                     },
                 )
 
-                if action == "opened":
+                if action is "opened":
                     await run_in_threadpool(
                         github_client.create_pr_review_comment,
                         installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
..................F
=================================== FAILURES ===================================
_____ test_missing_repo_config_with_invalid_service_defaults_fails_closed ______

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x796a51263150>
mock_github_client = <MagicMock name='github_client' id='133497531289952'>

    @pytest.mark.asyncio
    async def test_missing_repo_config_with_invalid_service_defaults_fails_closed(
        webhook_handler, mock_github_client
    ):
        """Test invalid service defaults block a PR when no repo config exists."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        service_defaults = MagicMock(spec=["approve_commands"])
        service_defaults.approve_commands = "approve"
    
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = service_defaults
            mock_settings.defaults = service_defaults
    
            result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result == {"status": "error", "message": "Invalid repository configuration"}
>       review_body = mock_github_client.create_pr_review_comment.call_args.args[3]
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AttributeError: 'NoneType' object has no attribute 'args'

tests/test_webhook_handler.py:1292: AttributeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:26:01.139380Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid service default configuration: approve_commands must be a list of strings"}, "event": "Invalid service default configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings", "logger": "stampbot.repo_policy", "level": "warning", "timestamp": "2026-11-04T03:26:01.139817Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "warning"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid service default configuration: approve_commands must be a list of strings"}, "event": "Invalid Stampbot configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:26:01.139905Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:26:01.139380Z'}
WARNING  stampbot.repo_policy:repo_policy.py:142 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid service default configuration: approve_commands must be a list of strings'}, 'event': 'Invalid service default configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings', 'logger': 'stampbot.repo_policy', 'level': 'warning', 'timestamp': '2026-11-04T03:26:01.139817Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:233 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid service default configuration: approve_commands must be a list of strings'}, 'event': 'Invalid Stampbot configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:26:01.139905Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:247
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:247: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if action is "opened":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_missing_repo_config_with_invalid_service_defaults_fails_closed - AttributeError: 'NoneType' object has no attribute 'args'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 450 passed, 3 deselected, 2 warnings in 3.32s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 5, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -286,7 +286,7 @@
                 set_span_ok(span)
                 result = dict(NO_ACTION)
 
-            if action == "opened":
+            if action is "opened":
                 # This only produces an operator warning, so it runs after the
                 # review decision instead of delaying it.
                 await self._warn_missing_approval_labels(event)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d6a69b13e70>
mock_github_client = <MagicMock name='github_client' id='137896015166304'>

    @pytest.mark.asyncio
    async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
        """Test missing approval label triggers label check after the review decision."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        mock_github_client.repo_has_label.return_value = False
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        # The label on the pull request exists by definition; only "stamp" is checked.
>       mock_github_client.repo_has_label.assert_called_once_with(12345, "octocat/hello-world", "stamp")

tests/test_webhook_handler.py:352: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.repo_has_label' id='137896015151856'>
args = (12345, 'octocat/hello-world', 'stamp'), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:21:33.173770Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:21:33.174191Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:21:33.174382Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:21:33.173770Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:21:33.174191Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:21:33.174382Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:289
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:289: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if action is "opened":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 382 passed, 3 deselected, 2 warnings in 2.47s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 6, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -306,7 +306,7 @@
         if not config.auto_approve_on_label:
             return Decision.NONE, None
 
-        if event.action == "unlabeled":
+        if event.action is "unlabeled":
             if event.event_label in config.approval_labels:
                 return Decision.DISMISS, event.event_label
             return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x70ce15b4a030>
mock_github_client = <MagicMock name='github_client' id='124030457644432'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "error"
E       AssertionError: assert 'ignored' == 'error'
E         
E         - error
E         + ignored

tests/test_webhook_handler.py:210: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:41.549256Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:09:41.549665Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:41.549256Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:09:41.549665Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:309
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:309: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if event.action is "unlabeled":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - AssertionError: assert 'ignored' == 'error'
  
  - error
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 2 warnings in 2.43s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 7, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -322,7 +322,7 @@
             # any duplicate afterwards.
             return Decision.APPROVE, label
 
-        if event.action == "labeled":
+        if event.action is "labeled":
             if event.event_label in config.approval_labels:
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x762107562520>
mock_github_client = <MagicMock name='github_client' id='129884324183552'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
>       assert [r["status"] for r in results] == ["success", "success"]
E       AssertionError: assert ['success', 'ignored'] == ['success', 'success']
E         
E         At index 1 diff: 'ignored' != 'success'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E               'success',
E         -     'success',
E         +     'ignored',
E           ]

tests/test_webhook_handler.py:116: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:21:43.804436Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:21:43.804932Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:21:43.805128Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:21:43.805660Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:21:43.804436Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:21:43.804932Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:21:43.805128Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:21:43.805660Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:325
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:325: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if event.action is "labeled":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: assert ['success', 'ignored'] == ['success', 'success']
  
  At index 1 diff: 'ignored' != 'success'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        'success',
  -     'success',
  +     'ignored',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 2 warnings in 2.39s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 8, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -327,7 +327,7 @@
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
 
-        if event.action == "synchronize" and config.reapprove:
+        if event.action is "synchronize" and config.reapprove:
             return Decision.REFRESH, label
 
         return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:330
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:330: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if event.action is "synchronize" and config.reapprove:

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.37s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 9, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -586,7 +586,7 @@
         def covers_head(review: BotReview) -> bool:
             commit = review.get("commit_id")
             if proven_only:
-                return bool(head_sha) and commit == head_sha
+                return bool(head_sha) and commit is head_sha
             return not head_sha or not commit or commit == head_sha
 
         return sorted(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x78fcccc83df0>
mock_github_client = <MagicMock name='github_client' id='133027184529424'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='133027184533120'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:36.616221Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:42:36.616649Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:36.616844Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:36.616221Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:42:36.616649Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:36.616844Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 10, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -587,7 +587,7 @@
             commit = review.get("commit_id")
             if proven_only:
                 return bool(head_sha) and commit == head_sha
-            return not head_sha or not commit or commit == head_sha
+            return not head_sha or not commit or commit is head_sha
 
         return sorted(
             review["id"]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x741d3786e520>
mock_github_client = <MagicMock name='github_client' id='127668857421312'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='127668857411904'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:12:22.265414Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:12:22.265890Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:12:22.266082Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:12:22.266583Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:12:22.266755Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:12:22.265414Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:12:22.265890Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:12:22.266082Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:12:22.266583Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:12:22.266755Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 11, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -592,7 +592,7 @@
         return sorted(
             review["id"]
             for review in reviews
-            if review["state"] == "APPROVED" and covers_head(review)
+            if review["state"] is "APPROVED" and covers_head(review)
         )
 
     def _has_current_active_approval(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:595
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:595: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if review["state"] is "APPROVED" and covers_head(review)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.38s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 12, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -704,7 +704,7 @@
             command = command_match.group(1).lower()
             add_span_attributes(span, {"chatops.command": command})
 
-            if command == "help":
+            if command is "help":
                 success = await self._post_help(
                     installation_id,
                     repo_full_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
..F
=================================== FAILURES ===================================
_______________________ test_issue_comment_help_command ________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x709e225dec80>
mock_github_client = <MagicMock name='github_client' id='123824476301904'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_command(webhook_handler, mock_github_client):
        """Test @stampbot help posts contextual help."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:974: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:12:22.046991Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:12:22.047430Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:12:22.046991Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:12:22.047430Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:707
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:707: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if command is "help":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_command - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 434 passed, 3 deselected, 2 warnings in 2.88s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -160,7 +160,7 @@
             )
 
             # Route to appropriate handler
-            if event_type == "pull_request":
+            if event_type is not "pull_request":
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x7f8076b9b240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'error' == 'ok'
E         
E         - ok
E         + error

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:13:06.138718Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "Missing required fields in PR event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:13:06.138842Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:13:06.139223Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:13:06.138718Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:212 {'event': 'Missing required fields in PR event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:13:06.138842Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:163
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:163: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if event_type is not "pull_request":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'error' == 'ok'
  
  - ok
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 2 warnings in 2.08s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 1, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -162,7 +162,7 @@
             # Route to appropriate handler
             if event_type == "pull_request":
                 result = await self._handle_pull_request(payload)
-            elif event_type == "pull_request_review_comment":
+            elif event_type is not "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
             elif event_type == "issue_comment":
                 # Issue comments can be on PRs too
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x7602a4d9b240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:37:39.110699Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:37:39.111265Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:37:39.110699Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:165
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:165: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    elif event_type is not "pull_request_review_comment":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 2 warnings in 2.13s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 2, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -164,7 +164,7 @@
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
-            elif event_type == "issue_comment":
+            elif event_type is not "issue_comment":
                 # Issue comments can be on PRs too
                 if "pull_request" in payload.get("issue", {}):
                     result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x78772845b240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:25:49.308775Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:25:49.309357Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:25:49.308775Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:167
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:167: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    elif event_type is not "issue_comment":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 2 warnings in 2.13s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 3, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -170,7 +170,7 @@
                     result = await self._handle_pr_comment(payload)
                 else:
                     result = {"status": "ignored", "message": "Not a PR comment"}
-            elif event_type == "ping":
+            elif event_type is not "ping":
                 result = {"status": "ok", "message": "pong"}
             else:
                 result = {"status": "ignored", "message": f"Event type {event_type} not handled"}
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
......................................................................F
=================================== FAILURES ===================================
_______________________________ test_ping_event ________________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7cfc21f53390>

    @pytest.mark.asyncio
    async def test_ping_event(webhook_handler):
        """Test handling ping event."""
        payload = {"zen": "Design for failure."}
        result = await webhook_handler.handle_event("ping", payload)
>       assert result["status"] == "ok"
E       AssertionError: assert 'ignored' == 'ok'
E         
E         - ok
E         + ignored

tests/test_webhook_handler.py:53: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:42:08.864284Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:42:08.864284Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:173
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:173: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    elif event_type is not "ping":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_ping_event - AssertionError: assert 'ignored' == 'ok'
  
  - ok
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 358 passed, 3 deselected, 2 warnings in 2.39s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 4, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -244,7 +244,7 @@
                     },
                 )
 
-                if action == "opened":
+                if action is not "opened":
                     await run_in_threadpool(
                         github_client.create_pr_review_comment,
                         installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
......................F
=================================== FAILURES ===================================
_______________ test_invalid_repo_config_no_review_on_non_opened _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75b914ebe5f0>
mock_github_client = <MagicMock name='github_client' id='129437778705584'>

    @pytest.mark.asyncio
    async def test_invalid_repo_config_no_review_on_non_opened(webhook_handler, mock_github_client):
        """Test invalid config skips review comment for non-opened events."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        payload["action"] = "labeled"
        mock_github_client.get_repo_file.return_value = 'chatops_required_permission = "invalid"'
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "error"
>       mock_github_client.create_pr_review_comment.assert_not_called()

tests/test_webhook_handler.py:1364: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.create_pr_review_comment' id='129437778702224'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'create_pr_review_comment' to not have been called. Called 1 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Stampbot configuration error:\n\nInvalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin\n\nPlease fix the configuration to re-enable automation.')].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:30:39.547135Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin"}, "event": "Invalid Stampbot configuration for octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:30:39.547602Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:30:39.547135Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:233 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin'}, 'event': 'Invalid Stampbot configuration for octocat/hello-world: Invalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:30:39.547602Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:247
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:247: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if action is not "opened":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_invalid_repo_config_no_review_on_non_opened - AssertionError: Expected 'create_pr_review_comment' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Stampbot configuration error:\n\nInvalid chatops_required_permission: invalid. Valid values: none, read, triage, write, maintain, admin\n\nPlease fix the configuration to re-enable automation.')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 454 passed, 3 deselected, 2 warnings in 3.33s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 5, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -286,7 +286,7 @@
                 set_span_ok(span)
                 result = dict(NO_ACTION)
 
-            if action == "opened":
+            if action is not "opened":
                 # This only produces an operator warning, so it runs after the
                 # review decision instead of delaying it.
                 await self._warn_missing_approval_labels(event)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:289
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:289: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if action is not "opened":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.33s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 6, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -306,7 +306,7 @@
         if not config.auto_approve_on_label:
             return Decision.NONE, None
 
-        if event.action == "unlabeled":
+        if event.action is not "unlabeled":
             if event.event_label in config.approval_labels:
                 return Decision.DISMISS, event.event_label
             return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7e18bba950f0>
mock_github_client = <MagicMock name='github_client' id='138644715640144'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:08:21.774955Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:08:21.775681Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:08:21.774955Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:08:21.775681Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:309
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:309: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if event.action is not "unlabeled":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 2 warnings in 2.40s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 7, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -322,7 +322,7 @@
             # any duplicate afterwards.
             return Decision.APPROVE, label
 
-        if event.action == "labeled":
+        if event.action is not "labeled":
             if event.event_label in config.approval_labels:
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.....................................F
=================================== FAILURES ===================================
____________________ test_pr_synchronize_reapprove_disabled ____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x785925ab5860>
mock_github_client = <MagicMock name='github_client' id='132324299120256'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_reapprove_disabled(webhook_handler, mock_github_client):
        """Test new commits do not re-approve by default."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "ignored"
        assert "no action" in result["message"].lower()
>       mock_github_client.find_bot_approval_reviews.assert_not_called()

tests/test_webhook_handler.py:531: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.find_bot_approval_reviews' id='132324299114880'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'find_bot_approval_reviews' to not have been called. Called 1 times.
E           Calls: [call(12345, 'octocat/hello-world', 42)].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:18:09.761205Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:18:09.761606Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:18:09.761205Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:18:09.761606Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:325
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:325: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if event.action is not "labeled":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_reapprove_disabled - AssertionError: Expected 'find_bot_approval_reviews' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42)].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 397 passed, 3 deselected, 2 warnings in 2.51s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 8, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -327,7 +327,7 @@
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
 
-        if event.action == "synchronize" and config.reapprove:
+        if event.action is not "synchronize" and config.reapprove:
             return Decision.REFRESH, label
 
         return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7706cd755a90>
mock_github_client = <MagicMock name='github_client' id='130871102075776'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:198: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:40:35.685039Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:40:35.685039Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:330
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:330: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if event.action is not "synchronize" and config.reapprove:

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 2 warnings in 2.48s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 9, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -586,7 +586,7 @@
         def covers_head(review: BotReview) -> bool:
             commit = review.get("commit_id")
             if proven_only:
-                return bool(head_sha) and commit == head_sha
+                return bool(head_sha) and commit is not head_sha
             return not head_sha or not commit or commit == head_sha
 
         return sorted(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75aed10abdf0>
mock_github_client = <MagicMock name='github_client' id='129393713795088'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='129393713798784'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 100, 'Duplicate Stampbot approval'),
E            call(12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:31:52.842226Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:31:52.842653Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:31:52.842837Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:31:52.842226Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:31:52.842653Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:31:52.842837Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 100, 'Duplicate Stampbot approval'),
 call(12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.45s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 10, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -587,7 +587,7 @@
             commit = review.get("commit_id")
             if proven_only:
                 return bool(head_sha) and commit == head_sha
-            return not head_sha or not commit or commit == head_sha
+            return not head_sha or not commit or commit is not head_sha
 
         return sorted(
             review["id"]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..........F
=================================== FAILURES ===================================
___________ test_active_approvals_for_head[reviews1-head-expected1] ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74334f6a2dd0>
reviews = [{'id': 1, 'state': 'APPROVED', 'commit_id': 'head'}]
head_sha = 'head', expected = [1]

    @pytest.mark.parametrize(
        ("reviews", "head_sha", "expected"),
        [
            ([], "head", []),
            ([{"id": 1, "state": "APPROVED", "commit_id": "head"}], "head", [1]),
            (
                [
                    {"id": 3, "state": "APPROVED", "commit_id": "head"},
                    {"id": 1, "state": "APPROVED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [1, 2, 3],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": "old"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": None},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                None,
                [1, 2],
            ),
            (
                [
                    {"id": 1, "state": "DISMISSED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
        ],
    )
    def test_active_approvals_for_head(webhook_handler, reviews, head_sha, expected):
        """Test which approvals count as covering the head, oldest first."""
>       assert webhook_handler._active_approvals_for_head(reviews, head_sha) == expected
E       assert [] == [1]
E         
E         Right contains one more item: 1
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + []
E         - [
E         -     1,
E         - ]

tests/test_webhook_handler.py:256: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_active_approvals_for_head[reviews1-head-expected1] - assert [] == [1]
  
  Right contains one more item: 1
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     1,
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 370 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 11, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -592,7 +592,7 @@
         return sorted(
             review["id"]
             for review in reviews
-            if review["state"] == "APPROVED" and covers_head(review)
+            if review["state"] is not "APPROVED" and covers_head(review)
         )
 
     def _has_current_active_approval(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x764c51326520>
mock_github_client = <MagicMock name='github_client' id='130070172777984'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='130070172768576'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:37:25.270520Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:37:25.270992Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:37:25.271208Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:37:25.271712Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:37:25.271882Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:37:25.270520Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:37:25.270992Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:37:25.271208Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:37:25.271712Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:37:25.271882Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:595
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:595: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if review["state"] is not "APPROVED" and covers_head(review)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 2 warnings in 2.43s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 12, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -704,7 +704,7 @@
             command = command_match.group(1).lower()
             add_span_attributes(span, {"chatops.command": command})
 
-            if command == "help":
+            if command is not "help":
                 success = await self._post_help(
                     installation_id,
                     repo_full_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.................................................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7affa3a67230>
mock_github_client = <MagicMock name='github_client' id='135238402056480'>

    @pytest.mark.asyncio
    async def test_issue_comment_approve(webhook_handler, mock_github_client):
        """Test @stampbot approve command."""
        payload = load_fixture("issue_comment_approve")
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
>       assert "approved" in result["message"].lower()
E       AssertionError: assert 'approved' in 'help message posted'
E        +  where 'help message posted' = <built-in method lower of str object at 0x7affa3d2d630>()
E        +    where <built-in method lower of str object at 0x7affa3d2d630> = 'Help message posted'.lower

tests/test_webhook_handler.py:830: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:28:09.055725Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:28:09.056228Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:28:09.055725Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:28:09.056228Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/webhook_handler.py:707
  /home/runner/work/stampbot/stampbot/stampbot/webhook_handler.py:707: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if command is not "help":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'approved' in 'help message posted'
 +  where 'help message posted' = <built-in method lower of str object at 0x7affa3d2d630>()
 +    where <built-in method lower of str object at 0x7affa3d2d630> = 'Help message posted'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 425 passed, 3 deselected, 2 warnings in 2.88s
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 0, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -624,7 +624,7 @@
         comment_body = comment.get("body", "")
 
         # Security: limit comment length to prevent DoS
-        if len(comment_body) > MAX_COMMENT_LENGTH:
+        if len(comment_body) == MAX_COMMENT_LENGTH:
             return {"status": "ignored", "message": "Comment too long"}
 
         comment_body = comment_body.lower().strip()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
F
=================================== FAILURES ===================================
_____________________ test_issue_comment_too_long_rejected _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x774624e96eb0>
mock_github_client = <MagicMock name='github_client' id='131143169079152'>

    @pytest.mark.asyncio
    async def test_issue_comment_too_long_rejected(webhook_handler, mock_github_client):
        """Test that comments exceeding max length are rejected to prevent DoS."""
        payload = load_fixture("issue_comment_approve")
        # Create an excessively long comment (> 64KB)
        payload["comment"]["body"] = "@stampbot approve " + "x" * 100000
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "ignored"
E       AssertionError: assert 'success' == 'ignored'
E         
E         - ignored
E         + success

tests/test_webhook_handler.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:34:37.382884Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:34:37.383355Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:34:37.382884Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:34:37.383355Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_too_long_rejected - AssertionError: assert 'success' == 'ignored'
  
  - ignored
  + success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 432 passed, 3 deselected, 1 warning in 2.92s
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 0, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -624,7 +624,7 @@
         comment_body = comment.get("body", "")
 
         # Security: limit comment length to prevent DoS
-        if len(comment_body) > MAX_COMMENT_LENGTH:
+        if len(comment_body) != MAX_COMMENT_LENGTH:
             return {"status": "ignored", "message": "Comment too long"}
 
         comment_body = comment_body.lower().strip()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x760543fa1010>
mock_github_client = <MagicMock name='github_client' id='129765073526064'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:14:54.924215Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:14:54.924215Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.40s
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 0, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -624,7 +624,7 @@
         comment_body = comment.get("body", "")
 
         # Security: limit comment length to prevent DoS
-        if len(comment_body) > MAX_COMMENT_LENGTH:
+        if len(comment_body) < MAX_COMMENT_LENGTH:
             return {"status": "ignored", "message": "Comment too long"}
 
         comment_body = comment_body.lower().strip()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7cadb3769d90>
mock_github_client = <MagicMock name='github_client' id='137085499514160'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:41:49.458472Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:41:49.458472Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 0, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -624,7 +624,7 @@
         comment_body = comment.get("body", "")
 
         # Security: limit comment length to prevent DoS
-        if len(comment_body) > MAX_COMMENT_LENGTH:
+        if len(comment_body) <= MAX_COMMENT_LENGTH:
             return {"status": "ignored", "message": "Comment too long"}
 
         comment_body = comment_body.lower().strip()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x76396a269d90>
mock_github_client = <MagicMock name='github_client' id='129988983561520'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:37:35.919816Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:37:35.919816Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 0, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -624,7 +624,7 @@
         comment_body = comment.get("body", "")
 
         # Security: limit comment length to prevent DoS
-        if len(comment_body) > MAX_COMMENT_LENGTH:
+        if len(comment_body) >= MAX_COMMENT_LENGTH:
             return {"status": "ignored", "message": "Comment too long"}
 
         comment_body = comment_body.lower().strip()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 0, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -624,7 +624,7 @@
         comment_body = comment.get("body", "")
 
         # Security: limit comment length to prevent DoS
-        if len(comment_body) > MAX_COMMENT_LENGTH:
+        if len(comment_body) is MAX_COMMENT_LENGTH:
             return {"status": "ignored", "message": "Comment too long"}
 
         comment_body = comment_body.lower().strip()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
F
=================================== FAILURES ===================================
_____________________ test_issue_comment_too_long_rejected _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x724083c6b380>
mock_github_client = <MagicMock name='github_client' id='125621504014192'>

    @pytest.mark.asyncio
    async def test_issue_comment_too_long_rejected(webhook_handler, mock_github_client):
        """Test that comments exceeding max length are rejected to prevent DoS."""
        payload = load_fixture("issue_comment_approve")
        # Create an excessively long comment (> 64KB)
        payload["comment"]["body"] = "@stampbot approve " + "x" * 100000
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "ignored"
E       AssertionError: assert 'success' == 'ignored'
E         
E         - ignored
E         + success

tests/test_webhook_handler.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:16:53.647973Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:16:53.648434Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:16:53.647973Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:16:53.648434Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_too_long_rejected - AssertionError: assert 'success' == 'ignored'
  
  - ignored
  + success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 432 passed, 3 deselected, 1 warning in 2.87s
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 0, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -624,7 +624,7 @@
         comment_body = comment.get("body", "")
 
         # Security: limit comment length to prevent DoS
-        if len(comment_body) > MAX_COMMENT_LENGTH:
+        if len(comment_body) is not MAX_COMMENT_LENGTH:
             return {"status": "ignored", "message": "Comment too long"}
 
         comment_body = comment_body.lower().strip()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7e5421561d90>
mock_github_client = <MagicMock name='github_client' id='138899821141296'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:39:49.670981Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:39:49.670981Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceComparisonOperator_Is_Eq, occurrence: 0, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -275,7 +275,7 @@
             )
 
             decision, label = self._decide(event)
-            if decision is Decision.APPROVE and label:
+            if decision == Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceComparisonOperator_Is_Eq, occurrence: 1, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -277,7 +277,7 @@
             decision, label = self._decide(event)
             if decision is Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
-            elif decision is Decision.REFRESH and label:
+            elif decision == Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
             elif decision is Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceComparisonOperator_Is_Eq, occurrence: 2, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -279,7 +279,7 @@
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
-            elif decision is Decision.DISMISS and label:
+            elif decision == Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
             else:
                 add_span_attributes(span, {"webhook.result": "no_action"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.41s
operator: core/ReplaceComparisonOperator_Is_Eq, occurrence: 3, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -531,7 +531,7 @@
                 event.repo_full_name,
                 label,
             )
-            if label_exists is False:
+            if label_exists == False:
                 logger.warning(
                     "Approval label %s not found in %s",
                     label,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Is_NotEq, occurrence: 0, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -275,7 +275,7 @@
             )
 
             decision, label = self._decide(event)
-            if decision is Decision.APPROVE and label:
+            if decision != Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x736e8d26d0f0>
mock_github_client = <MagicMock name='github_client' id='126918690411856'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:38:18.876519Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:38:18.877156Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:38:18.876519Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:38:18.877156Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.40s
operator: core/ReplaceComparisonOperator_Is_NotEq, occurrence: 1, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -277,7 +277,7 @@
             decision, label = self._decide(event)
             if decision is Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
-            elif decision is Decision.REFRESH and label:
+            elif decision != Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
             elif decision is Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x766e834d87d0>
mock_github_client = <MagicMock name='github_client' id='130217043407744'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:198: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:04:27.769325Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:04:27.769325Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceComparisonOperator_Is_NotEq, occurrence: 2, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -279,7 +279,7 @@
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
-            elif decision is Decision.DISMISS and label:
+            elif decision != Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
             else:
                 add_span_attributes(span, {"webhook.result": "no_action"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x789cb84e6030>
mock_github_client = <MagicMock name='github_client' id='132614524487056'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "error"
E       AssertionError: assert 'ignored' == 'error'
E         
E         - error
E         + ignored

tests/test_webhook_handler.py:210: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:12:43.291321Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:12:43.291721Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:12:43.291321Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:12:43.291721Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - AssertionError: assert 'ignored' == 'error'
  
  - error
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceComparisonOperator_Is_NotEq, occurrence: 3, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -531,7 +531,7 @@
                 event.repo_full_name,
                 label,
             )
-            if label_exists is False:
+            if label_exists != False:
                 logger.warning(
                     "Approval label %s not found in %s",
                     label,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Is_Lt, occurrence: 0, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -275,7 +275,7 @@
             )
 
             decision, label = self._decide(event)
-            if decision is Decision.APPROVE and label:
+            if decision < Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7875408590f0>
mock_github_client = <MagicMock name='github_client' id='132445011006800'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7875408590f0>
payload = {'action': 'opened', 'number': 42, 'pull_request': {'url': 'https://api.github.com/repos/octocat/hello-world/pulls/42'...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
>           if decision < Decision.APPROVE and label:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '<' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:278: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:54:59.320455Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:54:59.321552Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:54:59.320455Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:54:59.321552Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: '<' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceComparisonOperator_Is_Lt, occurrence: 1, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -277,7 +277,7 @@
             decision, label = self._decide(event)
             if decision is Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
-            elif decision is Decision.REFRESH and label:
+            elif decision < Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
             elif decision is Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7da72f7007d0>
mock_github_client = <MagicMock name='github_client' id='138157104935808'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7da72f7007d0>
payload = {'action': 'synchronize', 'number': 42, 'pull_request': {'url': 'https://api.github.com/repos/octocat/hello-world/pull...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
            if decision is Decision.APPROVE and label:
                result = await self._approve_for_label(span, event, label)
>           elif decision < Decision.REFRESH and label:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '<' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:280: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:32:21.589660Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:32:21.589660Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - TypeError: '<' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceComparisonOperator_Is_Lt, occurrence: 2, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -279,7 +279,7 @@
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
-            elif decision is Decision.DISMISS and label:
+            elif decision < Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
             else:
                 add_span_attributes(span, {"webhook.result": "no_action"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75379b0fa030>
mock_github_client = <MagicMock name='github_client' id='128881705174752'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:208: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x75379b0fa030>
payload = {'action': 'unlabeled', 'number': 42, 'label': {'id': 111111, 'node_id': 'LA_test1', 'url': 'https://api.github.com/re...', 'id': 123456789, 'node_id': 'PR_kwDOBtest', 'html_url': 'https://github.com/octocat/hello-world/pull/42', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
            if decision is Decision.APPROVE and label:
                result = await self._approve_for_label(span, event, label)
            elif decision is Decision.REFRESH and label:
                result = await self._refresh_for_label(span, event, label)
>           elif decision < Decision.DISMISS and label:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '<' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:282: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:12:08.506882Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:12:08.507294Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:12:08.506882Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:12:08.507294Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - TypeError: '<' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceComparisonOperator_Is_Lt, occurrence: 3, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -531,7 +531,7 @@
                 event.repo_full_name,
                 label,
             )
-            if label_exists is False:
+            if label_exists < False:
                 logger.warning(
                     "Approval label %s not found in %s",
                     label,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceComparisonOperator_Is_LtE, occurrence: 0, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -275,7 +275,7 @@
             )
 
             decision, label = self._decide(event)
-            if decision is Decision.APPROVE and label:
+            if decision <= Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7aae8e5750f0>
mock_github_client = <MagicMock name='github_client' id='134890157905232'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7aae8e5750f0>
payload = {'action': 'opened', 'number': 42, 'pull_request': {'url': 'https://api.github.com/repos/octocat/hello-world/pulls/42'...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
>           if decision <= Decision.APPROVE and label:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '<=' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:278: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:05:38.131145Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:05:38.131817Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:05:38.131145Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:05:38.131817Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: '<=' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceComparisonOperator_Is_LtE, occurrence: 1, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -277,7 +277,7 @@
             decision, label = self._decide(event)
             if decision is Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
-            elif decision is Decision.REFRESH and label:
+            elif decision <= Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
             elif decision is Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75c8b2d147d0>
mock_github_client = <MagicMock name='github_client' id='129504876671872'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x75c8b2d147d0>
payload = {'action': 'synchronize', 'number': 42, 'pull_request': {'url': 'https://api.github.com/repos/octocat/hello-world/pull...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
            if decision is Decision.APPROVE and label:
                result = await self._approve_for_label(span, event, label)
>           elif decision <= Decision.REFRESH and label:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '<=' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:280: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:29:38.901991Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:29:38.901991Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - TypeError: '<=' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceComparisonOperator_Is_LtE, occurrence: 2, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -279,7 +279,7 @@
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
-            elif decision is Decision.DISMISS and label:
+            elif decision <= Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
             else:
                 add_span_attributes(span, {"webhook.result": "no_action"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x735f70b7e030>
mock_github_client = <MagicMock name='github_client' id='126853770724752'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:208: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x735f70b7e030>
payload = {'action': 'unlabeled', 'number': 42, 'label': {'id': 111111, 'node_id': 'LA_test1', 'url': 'https://api.github.com/re...', 'id': 123456789, 'node_id': 'PR_kwDOBtest', 'html_url': 'https://github.com/octocat/hello-world/pull/42', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
            if decision is Decision.APPROVE and label:
                result = await self._approve_for_label(span, event, label)
            elif decision is Decision.REFRESH and label:
                result = await self._refresh_for_label(span, event, label)
>           elif decision <= Decision.DISMISS and label:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '<=' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:282: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:42:21.451161Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:42:21.451599Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:42:21.451161Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:42:21.451599Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - TypeError: '<=' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceComparisonOperator_Is_LtE, occurrence: 3, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -531,7 +531,7 @@
                 event.repo_full_name,
                 label,
             )
-            if label_exists is False:
+            if label_exists <= False:
                 logger.warning(
                     "Approval label %s not found in %s",
                     label,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_Is_Gt, occurrence: 0, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -275,7 +275,7 @@
             )
 
             decision, label = self._decide(event)
-            if decision is Decision.APPROVE and label:
+            if decision > Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74d707fd10f0>
mock_github_client = <MagicMock name='github_client' id='128466990290256'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x74d707fd10f0>
payload = {'action': 'opened', 'number': 42, 'pull_request': {'url': 'https://api.github.com/repos/octocat/hello-world/pulls/42'...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
>           if decision > Decision.APPROVE and label:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '>' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:278: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:34:30.823003Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:34:30.823821Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:34:30.823003Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:34:30.823821Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: '>' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.39s
operator: core/ReplaceComparisonOperator_Is_Gt, occurrence: 1, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -277,7 +277,7 @@
             decision, label = self._decide(event)
             if decision is Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
-            elif decision is Decision.REFRESH and label:
+            elif decision > Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
             elif decision is Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7b15e7551b80>
mock_github_client = <MagicMock name='github_client' id='135334027162496'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7b15e7551b80>
payload = {'action': 'synchronize', 'number': 42, 'pull_request': {'url': 'https://api.github.com/repos/octocat/hello-world/pull...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
            if decision is Decision.APPROVE and label:
                result = await self._approve_for_label(span, event, label)
>           elif decision > Decision.REFRESH and label:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '>' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:280: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:26:41.042744Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:26:41.042744Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - TypeError: '>' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceComparisonOperator_Is_Gt, occurrence: 2, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -279,7 +279,7 @@
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
-            elif decision is Decision.DISMISS and label:
+            elif decision > Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
             else:
                 add_span_attributes(span, {"webhook.result": "no_action"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f360b1e6030>
mock_github_client = <MagicMock name='github_client' id='139870111597968'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:208: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7f360b1e6030>
payload = {'action': 'unlabeled', 'number': 42, 'label': {'id': 111111, 'node_id': 'LA_test1', 'url': 'https://api.github.com/re...', 'id': 123456789, 'node_id': 'PR_kwDOBtest', 'html_url': 'https://github.com/octocat/hello-world/pull/42', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
            if decision is Decision.APPROVE and label:
                result = await self._approve_for_label(span, event, label)
            elif decision is Decision.REFRESH and label:
                result = await self._refresh_for_label(span, event, label)
>           elif decision > Decision.DISMISS and label:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '>' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:282: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:49:56.822477Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:49:56.822892Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:49:56.822477Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:49:56.822892Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - TypeError: '>' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceComparisonOperator_Is_Gt, occurrence: 3, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -531,7 +531,7 @@
                 event.repo_full_name,
                 label,
             )
-            if label_exists is False:
+            if label_exists > False:
                 logger.warning(
                     "Approval label %s not found in %s",
                     label,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceComparisonOperator_Is_GtE, occurrence: 0, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -275,7 +275,7 @@
             )
 
             decision, label = self._decide(event)
-            if decision is Decision.APPROVE and label:
+            if decision >= Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x755b16b650f0>
mock_github_client = <MagicMock name='github_client' id='129034105394512'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x755b16b650f0>
payload = {'action': 'opened', 'number': 42, 'pull_request': {'url': 'https://api.github.com/repos/octocat/hello-world/pulls/42'...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
>           if decision >= Decision.APPROVE and label:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '>=' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:278: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:14:47.854648Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:14:47.855379Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:14:47.854648Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:14:47.855379Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: '>=' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.37s
operator: core/ReplaceComparisonOperator_Is_GtE, occurrence: 1, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -277,7 +277,7 @@
             decision, label = self._decide(event)
             if decision is Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
-            elif decision is Decision.REFRESH and label:
+            elif decision >= Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
             elif decision is Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x725eaa3e07d0>
mock_github_client = <MagicMock name='github_client' id='125750930633600'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x725eaa3e07d0>
payload = {'action': 'synchronize', 'number': 42, 'pull_request': {'url': 'https://api.github.com/repos/octocat/hello-world/pull...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
            if decision is Decision.APPROVE and label:
                result = await self._approve_for_label(span, event, label)
>           elif decision >= Decision.REFRESH and label:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '>=' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:280: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:28:17.657634Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:28:17.657634Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - TypeError: '>=' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceComparisonOperator_Is_GtE, occurrence: 2, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -279,7 +279,7 @@
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
-            elif decision is Decision.DISMISS and label:
+            elif decision >= Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
             else:
                 add_span_attributes(span, {"webhook.result": "no_action"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7890dc906030>
mock_github_client = <MagicMock name='github_client' id='132563599263120'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:208: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7890dc906030>
payload = {'action': 'unlabeled', 'number': 42, 'label': {'id': 111111, 'node_id': 'LA_test1', 'url': 'https://api.github.com/re...', 'id': 123456789, 'node_id': 'PR_kwDOBtest', 'html_url': 'https://github.com/octocat/hello-world/pull/42', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
    
            decision, label = self._decide(event)
            if decision is Decision.APPROVE and label:
                result = await self._approve_for_label(span, event, label)
            elif decision is Decision.REFRESH and label:
                result = await self._refresh_for_label(span, event, label)
>           elif decision >= Decision.DISMISS and label:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '>=' not supported between instances of 'Decision' and 'Decision'

stampbot/webhook_handler.py:282: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:16:22.569615Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:16:22.570011Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:16:22.569615Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:16:22.570011Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - TypeError: '>=' not supported between instances of 'Decision' and 'Decision'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceComparisonOperator_Is_GtE, occurrence: 3, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -531,7 +531,7 @@
                 event.repo_full_name,
                 label,
             )
-            if label_exists is False:
+            if label_exists >= False:
                 logger.warning(
                     "Approval label %s not found in %s",
                     label,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 0, definition_name: webhook_secret
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -93,7 +93,7 @@
         Raises:
             RuntimeError: If webhook secret is not configured.
         """
-        if self._webhook_secret is None:
+        if self._webhook_secret is not None:
             if not settings.webhook_secret:
                 raise RuntimeError(
                     "Webhook secret not configured. Visit /setup to create your GitHub App."
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x75d7c9e829c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:118: in verify_signature
    "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/hmac.py:218: in new
    return HMAC(key, msg, digestmod)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <hmac.HMAC object at 0x75d7c86bcd10>, key = None
msg = b'{"test": "data"}', digestmod = <built-in function openssl_sha256>

    def __init__(self, key, msg=None, digestmod=''):
        """Create a new HMAC object.
    
        key: bytes or buffer, key for the keyed hash object.
        msg: bytes or buffer, Initial input for the hash or None.
        digestmod: A hash name suitable for hashlib.new(). *OR*
                   A hashlib constructor returning a new hash object. *OR*
                   A module supporting PEP 247.
    
                   Required as of 3.8, despite its position after the optional
                   msg argument.  Passing it as a keyword argument is
                   recommended, though not required for legacy API reasons.
        """
    
        if not isinstance(key, (bytes, bytearray)):
>           raise TypeError(f"key: expected bytes or bytearray, "
                            f"but got {type(key).__name__!r}")
E           TypeError: key: expected bytes or bytearray, but got 'NoneType'

/usr/lib/python3.14/hmac.py:71: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:19:16.749489Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: key: expected bytes or bytearray, but got 'NoneType'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.25s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 1, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -275,7 +275,7 @@
             )
 
             decision, label = self._decide(event)
-            if decision is Decision.APPROVE and label:
+            if decision is not Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c62f4b7d0f0>
mock_github_client = <MagicMock name='github_client' id='136764475987280'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:35:20.396904Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:35:20.397626Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:35:20.396904Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:35:20.397626Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 2, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -277,7 +277,7 @@
             decision, label = self._decide(event)
             if decision is Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
-            elif decision is Decision.REFRESH and label:
+            elif decision is not Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
             elif decision is Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75f8f77387d0>
mock_github_client = <MagicMock name='github_client' id='129712259822464'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:198: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:22:10.535826Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:22:10.535826Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 3, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -279,7 +279,7 @@
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
-            elif decision is Decision.DISMISS and label:
+            elif decision is not Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
             else:
                 add_span_attributes(span, {"webhook.result": "no_action"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x761111be2030>
mock_github_client = <MagicMock name='github_client' id='129815704290704'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "error"
E       AssertionError: assert 'ignored' == 'error'
E         
E         - error
E         + ignored

tests/test_webhook_handler.py:210: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:38:39.020779Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:38:39.021205Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:38:39.020779Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:38:39.021205Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - AssertionError: assert 'ignored' == 'error'
  
  - error
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 4, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -312,7 +312,7 @@
             return Decision.NONE, None
 
         label = self._find_approval_label(event.labels, config)
-        if label is None:
+        if label is not None:
             return Decision.NONE, None
 
         if event.action in ("opened", "reopened"):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fba127990f0>
mock_github_client = <MagicMock name='github_client' id='140437173425488'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:19:58.110321Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:19:58.111265Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:19:58.110321Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:19:58.111265Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.38s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 5, definition_name: _refresh_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -366,7 +366,7 @@
             event.repo_full_name,
             event.pr_number,
         )
-        if reviews is None:
+        if reviews is not None:
             # GitHub could not be read. Approving may create a duplicate that
             # the cleanup removes; skipping could miss an approval for good.
             logger.warning(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x71a9bb6087d0>
mock_github_client = <MagicMock name='github_client' id='124973903115136'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:198: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:51:50.629833Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:51:50.629833Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.45s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 6, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -531,7 +531,7 @@
                 event.repo_full_name,
                 label,
             )
-            if label_exists is False:
+            if label_exists is not False:
                 logger.warning(
                     "Approval label %s not found in %s",
                     label,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 7, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -959,7 +959,7 @@
                     repo_full_name,
                     pr_number,
                 )
-                if reviews is None:
+                if reviews is not None:
                     # Unknown state. A duplicate is recoverable; a missed
                     # approval is not, so approve and let the cleanup sort it out.
                     logger.warning(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7b8f8ed0e520>
mock_github_client = <MagicMock name='github_client' id='135856527670784'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='135856527661376'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:08:18.223369Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:08:18.223856Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:08:18.224053Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42}, "event": "Could not read reviews for PR #42 in octocat/hello-world; approving without a check", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:08:18.224232Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:08:18.224651Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:08:18.224836Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42}, "event": "Could not read reviews for PR #42 in octocat/hello-world; approving without a check", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:08:18.224991Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:08:18.223369Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:08:18.223856Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:08:18.224053Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:965 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'Could not read reviews for PR #42 in octocat/hello-world; approving without a check', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:08:18.224232Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:08:18.224651Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:08:18.224836Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:965 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'Could not read reviews for PR #42 in octocat/hello-world; approving without a check', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:08:18.224991Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 8, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1075,7 +1075,7 @@
                     repo_full_name,
                     pr_number,
                 )
-                if reviews is None:
+                if reviews is not None:
                     # Nothing can be dismissed without the list, and the
                     # failure has to reach the metric operators watch.
                     raise RuntimeError("Stampbot reviews could not be listed")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7b821e68fdf0>
mock_github_client = <MagicMock name='github_client' id='135798807973904'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='135798807976928'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:49.785897Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:42:49.786330Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:49.786520Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "Stampbot reviews could not be listed"}, "event": "Error dismissing approvals: Stampbot reviews could not be listed", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T04:42:49.786880Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:49.785897Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:42:49.786330Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:49.786520Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': 'Stampbot reviews could not be listed'}, 'event': 'Error dismissing approvals: Stampbot reviews could not be listed', 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T04:42:49.786880Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceUnaryOperator_USub_UAdd, occurrence: 0, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -637,7 +637,7 @@
         if "pull_request" in payload.get("issue", {}):
             pr_url = payload["issue"]["pull_request"]["url"]
             # Extract PR number from URL
-            pr_number = int(pr_url.split("/")[-1])
+            pr_number = int(pr_url.split("/")[+1])
         elif "pull_request" in payload:
             pr_number = payload["pull_request"]["number"]
         else:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f2d3e161d90>
mock_github_client = <MagicMock name='github_client' id='139832311323952'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7f2d3e161d90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
>           pr_number = int(pr_url.split("/")[+1])
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^
E           ValueError: invalid literal for int() with base 10: ''

stampbot/webhook_handler.py:640: ValueError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:14:51.406337Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:14:51.406337Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - ValueError: invalid literal for int() with base 10: ''
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceUnaryOperator_USub_Invert, occurrence: 0, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -637,7 +637,7 @@
         if "pull_request" in payload.get("issue", {}):
             pr_url = payload["issue"]["pull_request"]["url"]
             # Extract PR number from URL
-            pr_number = int(pr_url.split("/")[-1])
+            pr_number = int(pr_url.split("/")[~1])
         elif "pull_request" in payload:
             pr_number = payload["pull_request"]["number"]
         else:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x761b70365d90>
mock_github_client = <MagicMock name='github_client' id='129860238155056'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x761b70365d90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
>           pr_number = int(pr_url.split("/")[~1])
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^
E           ValueError: invalid literal for int() with base 10: 'pulls'

stampbot/webhook_handler.py:640: ValueError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:24:04.891569Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:24:04.891569Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - ValueError: invalid literal for int() with base 10: 'pulls'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.46s
operator: core/ReplaceUnaryOperator_USub_Not, occurrence: 0, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -637,7 +637,7 @@
         if "pull_request" in payload.get("issue", {}):
             pr_url = payload["issue"]["pull_request"]["url"]
             # Extract PR number from URL
-            pr_number = int(pr_url.split("/")[-1])
+            pr_number = int(pr_url.split("/")[not 1])
         elif "pull_request" in payload:
             pr_number = payload["pull_request"]["number"]
         else:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c8046d69d90>
mock_github_client = <MagicMock name='github_client' id='136890403527984'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7c8046d69d90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
>           pr_number = int(pr_url.split("/")[not 1])
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           ValueError: invalid literal for int() with base 10: 'https:'

stampbot/webhook_handler.py:640: ValueError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:36:19.732849Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:36:19.732849Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - ValueError: invalid literal for int() with base 10: 'https:'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.45s
operator: core/ReplaceUnaryOperator_Delete_USub, occurrence: 0, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -637,7 +637,7 @@
         if "pull_request" in payload.get("issue", {}):
             pr_url = payload["issue"]["pull_request"]["url"]
             # Extract PR number from URL
-            pr_number = int(pr_url.split("/")[-1])
+            pr_number = int(pr_url.split("/")[1])
         elif "pull_request" in payload:
             pr_number = payload["pull_request"]["number"]
         else:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x793be4565d90>
mock_github_client = <MagicMock name='github_client' id='133298160246064'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x793be4565d90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
>           pr_number = int(pr_url.split("/")[1])
                        ^^^^^^^^^^^^^^^^^^^^^^^^^
E           ValueError: invalid literal for int() with base 10: ''

stampbot/webhook_handler.py:640: ValueError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:02:21.275535Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:02:21.275535Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - ValueError: invalid literal for int() with base 10: ''
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0, definition_name: webhook_secret
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -94,7 +94,7 @@
             RuntimeError: If webhook secret is not configured.
         """
         if self._webhook_secret is None:
-            if not settings.webhook_secret:
+            if  settings.webhook_secret:
                 raise RuntimeError(
                     "Webhook secret not configured. Visit /setup to create your GitHub App."
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x71170eb829c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:118: in verify_signature
    "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
                         ^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x71170dd36f90>

    @property
    def webhook_secret(self) -> bytes:
        """Get webhook secret, initializing if needed.
    
        Returns:
            Webhook secret as bytes.
    
        Raises:
            RuntimeError: If webhook secret is not configured.
        """
        if self._webhook_secret is None:
            if  settings.webhook_secret:
>               raise RuntimeError(
                    "Webhook secret not configured. Visit /setup to create your GitHub App."
                )
E               RuntimeError: Webhook secret not configured. Visit /setup to create your GitHub App.

stampbot/webhook_handler.py:98: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:48:56.516222Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - RuntimeError: Webhook secret not configured. Visit /setup to create your GitHub App.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.28s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -111,7 +111,7 @@
         Returns:
             True if signature is valid
         """
-        if not signature:
+        if  signature:
             return False
 
         expected_signature = (
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.............F
=================================== FAILURES ===================================
__________________________ test_webhook_invalid_json ___________________________

test_client = <starlette.testclient.TestClient object at 0x7a3aa3a7bce0>

    def test_webhook_invalid_json(test_client: TestClient):
        """Test webhook rejects invalid JSON payload."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        # Create a valid signature for invalid JSON
        body = b"not valid json"
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
>       assert response.status_code == 400
E       assert 401 == 400
E        +  where 401 = <Response [401 Unauthorized]>.status_code

tests/test_main.py:335: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Invalid webhook signature", "client_ip": "testclient", "logger": "stampbot.main", "level": "warning", "timestamp": "2026-11-04T03:25:56.672764Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 401 Unauthorized\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:25:56.673282Z"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.main:main.py:512 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'warning', 'timestamp': '2026-11-04T03:25:56.672764Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_invalid_json - assert 401 == 400
 +  where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 229 passed, 3 deselected, 1 warning in 2.13s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -208,7 +208,7 @@
                 "github.action": action or "unknown",
             },
         ) as span:
-            if not all([pr_number, repo_full_name, installation_id]):
+            if  all([pr_number, repo_full_name, installation_id]):
                 logger.warning("Missing required fields in PR event")
                 add_span_attributes(span, {"webhook.result": "missing_fields"})
                 set_span_ok(span)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7b49188210f0>
mock_github_client = <MagicMock name='github_client' id='135553895012000'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:12:51.715647Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "Missing required fields in PR event", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:12:51.715753Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:12:51.715647Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:212 {'event': 'Missing required fields in PR event', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:12:51.715753Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.36s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 3, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -303,7 +303,7 @@
             The decision and the approval label it concerns, if any.
         """
         config = event.repo_config
-        if not config.auto_approve_on_label:
+        if  config.auto_approve_on_label:
             return Decision.NONE, None
 
         if event.action == "unlabeled":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7b26ef3450f0>
mock_github_client = <MagicMock name='github_client' id='135407174063440'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:30:05.049533Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:30:05.050519Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:30:05.049533Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:30:05.050519Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 4, definition_name: _refresh_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -375,7 +375,7 @@
                 event.repo_full_name,
                 extra={"repo": event.repo_full_name, "pr_number": event.pr_number},
             )
-        elif not self._approval_needs_refresh(reviews, event.head_sha):
+        elif  self._approval_needs_refresh(reviews, event.head_sha):
             add_span_attributes(span, {"webhook.result": "no_action"})
             set_span_ok(span)
             return dict(NO_ACTION)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...................................F
=================================== FAILURES ===================================
_________ test_pr_labeled_unrelated_label_with_approval_label_ignored __________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77b31ead44b0>
mock_github_client = <MagicMock name='github_client' id='131611218641648'>

    @pytest.mark.asyncio
    async def test_pr_labeled_unrelated_label_with_approval_label_ignored(
        webhook_handler, mock_github_client
    ):
        """Test unrelated label changes do not re-approve just because an approval label exists."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["label"] = {"name": "bug"}
        payload["pull_request"]["labels"].append({"name": "bug"})
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "ignored"
E       AssertionError: assert 'success' == 'ignored'
E         
E         - ignored
E         + success

tests/test_webhook_handler.py:496: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:20:39.207514Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:20:39.207917Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:20:39.208216Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:20:39.207514Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:20:39.207917Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:20:39.208216Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_unrelated_label_with_approval_label_ignored - AssertionError: assert 'success' == 'ignored'
  
  - ignored
  + success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 395 passed, 3 deselected, 1 warning in 2.46s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 5, definition_name: _approve_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -420,7 +420,7 @@
             pr_author,
             author_team_slugs,
         )
-        if not is_eligible:
+        if  is_eligible:
             logger.info(
                 "PR #%d not eligible for auto-approval: %s",
                 event.pr_number,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f4e8b7ed0f0>
mock_github_client = <MagicMock name='github_client' id='139975345870160'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:22:43.445937Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:22:43.447035Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "pr_author": "contributor", "reason": null}, "event": "PR #42 not eligible for auto-approval: None", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:22:43.447439Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:22:43.445937Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:22:43.447035Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:424 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'pr_author': 'contributor', 'reason': None}, 'event': 'PR #42 not eligible for auto-approval: None', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:22:43.447439Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 6, definition_name: _approval_needs_refresh
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -553,7 +553,7 @@
         Returns:
             True when a prior approval exists but no active approval applies to the current head.
         """
-        if not reviews:
+        if  reviews:
             return False
 
         return not self._has_current_active_approval(reviews, head_sha)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...................................F
=================================== FAILURES ===================================
_________ test_pr_labeled_unrelated_label_with_approval_label_ignored __________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x756c85d38050>
mock_github_client = <MagicMock name='github_client' id='129108981842672'>

    @pytest.mark.asyncio
    async def test_pr_labeled_unrelated_label_with_approval_label_ignored(
        webhook_handler, mock_github_client
    ):
        """Test unrelated label changes do not re-approve just because an approval label exists."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["label"] = {"name": "bug"}
        payload["pull_request"]["labels"].append({"name": "bug"})
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "ignored"
E       AssertionError: assert 'success' == 'ignored'
E         
E         - ignored
E         + success

tests/test_webhook_handler.py:496: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:00:03.023084Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:00:03.023515Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:00:03.023798Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:00:03.023084Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:00:03.023515Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:00:03.023798Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_unrelated_label_with_approval_label_ignored - AssertionError: assert 'success' == 'ignored'
  
  - ignored
  + success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 395 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 7, definition_name: _approval_needs_refresh
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -556,7 +556,7 @@
         if not reviews:
             return False
 
-        return not self._has_current_active_approval(reviews, head_sha)
+        return  self._has_current_active_approval(reviews, head_sha)
 
     def _active_approvals_for_head(
         self,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
....................................F
=================================== FAILURES ===================================
_________ test_pr_labeled_unrelated_label_reapproves_dismissed_review __________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x796cb353c590>
mock_github_client = <MagicMock name='github_client' id='133507792719888'>

    @pytest.mark.asyncio
    async def test_pr_labeled_unrelated_label_reapproves_dismissed_review(
        webhook_handler, mock_github_client
    ):
        """Test unrelated label changes re-approve when prior Stampbot approval was dismissed."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["label"] = {"name": "bug"}
        payload["pull_request"]["labels"].append({"name": "bug"})
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 123, "state": "DISMISSED", "commit_id": "abc123def456"}
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:516: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:36:23.403333Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:36:23.403742Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:36:23.403333Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:36:23.403742Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_unrelated_label_reapproves_dismissed_review - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 396 passed, 3 deselected, 1 warning in 2.49s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 8, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -587,7 +587,7 @@
             commit = review.get("commit_id")
             if proven_only:
                 return bool(head_sha) and commit == head_sha
-            return not head_sha or not commit or commit == head_sha
+            return  head_sha or not commit or commit == head_sha
 
         return sorted(
             review["id"]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
............F
=================================== FAILURES ===================================
___________ test_active_approvals_for_head[reviews3-head-expected3] ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a574b28e690>
reviews = [{'id': 1, 'state': 'APPROVED', 'commit_id': 'old'}, {'id': 2, 'state': 'APPROVED', 'commit_id': 'head'}]
head_sha = 'head', expected = [2]

    @pytest.mark.parametrize(
        ("reviews", "head_sha", "expected"),
        [
            ([], "head", []),
            ([{"id": 1, "state": "APPROVED", "commit_id": "head"}], "head", [1]),
            (
                [
                    {"id": 3, "state": "APPROVED", "commit_id": "head"},
                    {"id": 1, "state": "APPROVED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [1, 2, 3],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": "old"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": None},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                None,
                [1, 2],
            ),
            (
                [
                    {"id": 1, "state": "DISMISSED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
        ],
    )
    def test_active_approvals_for_head(webhook_handler, reviews, head_sha, expected):
        """Test which approvals count as covering the head, oldest first."""
>       assert webhook_handler._active_approvals_for_head(reviews, head_sha) == expected
E       assert [1, 2] == [2]
E         
E         At index 0 diff: 1 != 2
E         Left contains one more item: 2
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         +     1,
E               2,
E           ]

tests/test_webhook_handler.py:256: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_active_approvals_for_head[reviews3-head-expected3] - assert [1, 2] == [2]
  
  At index 0 diff: 1 != 2
  Left contains one more item: 2
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  +     1,
        2,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 372 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 9, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -587,7 +587,7 @@
             commit = review.get("commit_id")
             if proven_only:
                 return bool(head_sha) and commit == head_sha
-            return not head_sha or not commit or commit == head_sha
+            return not head_sha or  commit or commit == head_sha
 
         return sorted(
             review["id"]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
............F
=================================== FAILURES ===================================
___________ test_active_approvals_for_head[reviews3-head-expected3] ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c8d71d7e5d0>
reviews = [{'id': 1, 'state': 'APPROVED', 'commit_id': 'old'}, {'id': 2, 'state': 'APPROVED', 'commit_id': 'head'}]
head_sha = 'head', expected = [2]

    @pytest.mark.parametrize(
        ("reviews", "head_sha", "expected"),
        [
            ([], "head", []),
            ([{"id": 1, "state": "APPROVED", "commit_id": "head"}], "head", [1]),
            (
                [
                    {"id": 3, "state": "APPROVED", "commit_id": "head"},
                    {"id": 1, "state": "APPROVED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [1, 2, 3],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": "old"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": None},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                None,
                [1, 2],
            ),
            (
                [
                    {"id": 1, "state": "DISMISSED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
        ],
    )
    def test_active_approvals_for_head(webhook_handler, reviews, head_sha, expected):
        """Test which approvals count as covering the head, oldest first."""
>       assert webhook_handler._active_approvals_for_head(reviews, head_sha) == expected
E       assert [1, 2] == [2]
E         
E         At index 0 diff: 1 != 2
E         Left contains one more item: 2
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         +     1,
E               2,
E           ]

tests/test_webhook_handler.py:256: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_active_approvals_for_head[reviews3-head-expected3] - assert [1, 2] == [2]
  
  At index 0 diff: 1 != 2
  Left contains one more item: 2
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  +     1,
        2,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 372 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 10, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -660,7 +660,7 @@
                 "chatops.commenter": commenter,
             },
         ) as span:
-            if not all([pr_number, repo_full_name, installation_id]):
+            if  all([pr_number, repo_full_name, installation_id]):
                 logger.warning("Missing required fields in comment event")
                 add_span_attributes(span, {"chatops.result": "missing_fields"})
                 set_span_ok(span)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a96542f5d90>
mock_github_client = <MagicMock name='github_client' id='134786093669680'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:47:19.014977Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "Missing required fields in comment event", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:47:19.015091Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:47:19.014977Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:664 {'event': 'Missing required fields in comment event', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:47:19.015091Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 11, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -688,7 +688,7 @@
                     "message": "Invalid repository configuration",
                 }
 
-            if not repo_config.chatops_enabled:
+            if  repo_config.chatops_enabled:
                 add_span_attributes(span, {"chatops.result": "disabled"})
                 set_span_ok(span)
                 return {"status": "ignored", "message": "Chatops not enabled"}
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7100aba79d90>
mock_github_client = <MagicMock name='github_client' id='124247712275760'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:33:25.441134Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:33:25.441571Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:33:25.441134Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:33:25.441571Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 12, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -695,7 +695,7 @@
 
             # Parse command
             command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
-            if not command_match:
+            if  command_match:
                 chatops_commands_total.labels(command="none", status="ignored").inc()
                 add_span_attributes(span, {"chatops.result": "no_command"})
                 set_span_ok(span)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x72834c365d90>
mock_github_client = <MagicMock name='github_client' id='125908264312112'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:18:58.508761Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:18:58.509214Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:18:58.508761Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:18:58.509214Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.47s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 13, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -735,7 +735,7 @@
                     commenter,
                     repo_config.chatops_required_permission,
                 )
-                if not has_permission:
+                if  has_permission:
                     chatops_commands_total.labels(command=command, status="forbidden").inc()
                     add_span_attributes(
                         span,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x760b06a70c20>
mock_github_client = <MagicMock name='github_client' id='129789747764528'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:09:56.941407Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:09:56.941828Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:09:56.941407Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:09:56.941828Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 14, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -952,7 +952,7 @@
                 "approval.trigger_type": trigger_type,
             },
         ) as span:
-            if not skip_existing_check:
+            if  skip_existing_check:
                 reviews = await run_in_threadpool(
                     github_client.find_bot_approval_reviews,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f6fc376e520>
mock_github_client = <MagicMock name='github_client' id='140118019276288'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='140118019266880'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:26:32.344882Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:26:32.345375Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:26:32.345581Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:26:32.346008Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:26:32.346191Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:26:32.344882Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:26:32.345375Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:26:32.345581Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:26:32.346008Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:26:32.346191Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 15, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1010,7 +1010,7 @@
                 status=status,
             ).inc()
 
-            if not success:
+            if  success:
                 errors_total.labels(error_type="approval_failed").inc()
             else:
                 # Another replica may have approved the same head in the same
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7495f94c3df0>
mock_github_client = <MagicMock name='github_client' id='128187504414736'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='128187504418432'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:39:53.182577Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:39:53.182988Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:39:53.183195Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:39:53.182577Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:39:53.182988Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:39:53.183195Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 16, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1090,7 +1090,7 @@
 
                 add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
 
-                if not review_ids:
+                if  review_ids:
                     logger.debug(
                         "No Stampbot approvals to dismiss on PR #%d",
                         pr_number,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7bbe194c3df0>
mock_github_client = <MagicMock name='github_client' id='136056421421072'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='136056421424768'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:58:36.594316Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:58:36.594736Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:58:36.594921Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:58:36.594316Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:58:36.594736Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:58:36.594921Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.40s
operator: core/AddNot, occurrence: 0, definition_name: head_sha
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -72,7 +72,7 @@
     def head_sha(self) -> str | None:
         """Current head commit, when GitHub included it."""
         sha = self.pr.get("head", {}).get("sha")
-        return str(sha) if sha else None
+        return str(sha) if not sha else None
 
 
 class WebhookHandler:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75e17bac3df0>
mock_github_client = <MagicMock name='github_client' id='129611325902864'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='129611325906560'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:31:36.569316Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:31:36.569723Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:31:36.569905Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:31:36.569316Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:31:36.569723Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:31:36.569905Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.44s
operator: core/AddNot, occurrence: 1, definition_name: webhook_secret
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -93,7 +93,7 @@
         Raises:
             RuntimeError: If webhook secret is not configured.
         """
-        if self._webhook_secret is None:
+        if not self._webhook_secret is None:
             if not settings.webhook_secret:
                 raise RuntimeError(
                     "Webhook secret not configured. Visit /setup to create your GitHub App."
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x72c5d36729c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:118: in verify_signature
    "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/hmac.py:218: in new
    return HMAC(key, msg, digestmod)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <hmac.HMAC object at 0x72c5d1ff8590>, key = None
msg = b'{"test": "data"}', digestmod = <built-in function openssl_sha256>

    def __init__(self, key, msg=None, digestmod=''):
        """Create a new HMAC object.
    
        key: bytes or buffer, key for the keyed hash object.
        msg: bytes or buffer, Initial input for the hash or None.
        digestmod: A hash name suitable for hashlib.new(). *OR*
                   A hashlib constructor returning a new hash object. *OR*
                   A module supporting PEP 247.
    
                   Required as of 3.8, despite its position after the optional
                   msg argument.  Passing it as a keyword argument is
                   recommended, though not required for legacy API reasons.
        """
    
        if not isinstance(key, (bytes, bytearray)):
>           raise TypeError(f"key: expected bytes or bytearray, "
                            f"but got {type(key).__name__!r}")
E           TypeError: key: expected bytes or bytearray, but got 'NoneType'

/usr/lib/python3.14/hmac.py:71: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:59:07.377494Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: key: expected bytes or bytearray, but got 'NoneType'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.19s
operator: core/AddNot, occurrence: 2, definition_name: webhook_secret
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -94,7 +94,7 @@
             RuntimeError: If webhook secret is not configured.
         """
         if self._webhook_secret is None:
-            if not settings.webhook_secret:
+            if not not settings.webhook_secret:
                 raise RuntimeError(
                     "Webhook secret not configured. Visit /setup to create your GitHub App."
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x75df141769c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:118: in verify_signature
    "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
                         ^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x75df1332ef90>

    @property
    def webhook_secret(self) -> bytes:
        """Get webhook secret, initializing if needed.
    
        Returns:
            Webhook secret as bytes.
    
        Raises:
            RuntimeError: If webhook secret is not configured.
        """
        if self._webhook_secret is None:
            if not not settings.webhook_secret:
>               raise RuntimeError(
                    "Webhook secret not configured. Visit /setup to create your GitHub App."
                )
E               RuntimeError: Webhook secret not configured. Visit /setup to create your GitHub App.

stampbot/webhook_handler.py:98: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:15:47.895121Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - RuntimeError: Webhook secret not configured. Visit /setup to create your GitHub App.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.22s
operator: core/AddNot, occurrence: 3, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -111,7 +111,7 @@
         Returns:
             True if signature is valid
         """
-        if not signature:
+        if not not signature:
             return False
 
         expected_signature = (
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.............F
=================================== FAILURES ===================================
__________________________ test_webhook_invalid_json ___________________________

test_client = <starlette.testclient.TestClient object at 0x76f56ec83ce0>

    def test_webhook_invalid_json(test_client: TestClient):
        """Test webhook rejects invalid JSON payload."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        # Create a valid signature for invalid JSON
        body = b"not valid json"
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
>       assert response.status_code == 400
E       assert 401 == 400
E        +  where 401 = <Response [401 Unauthorized]>.status_code

tests/test_main.py:335: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Invalid webhook signature", "client_ip": "testclient", "logger": "stampbot.main", "level": "warning", "timestamp": "2026-11-04T03:08:14.886345Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 401 Unauthorized\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:08:14.886839Z"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.main:main.py:512 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'warning', 'timestamp': '2026-11-04T03:08:14.886345Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_invalid_json - assert 401 == 400
 +  where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 229 passed, 3 deselected, 1 warning in 2.12s
operator: core/AddNot, occurrence: 4, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -149,7 +149,7 @@
             "webhook.event_type": event_type,
             "webhook.action": action,
         }
-        if delivery_id:
+        if not delivery_id:
             span_attributes["github.delivery_id"] = delivery_id
 
         with create_span("webhook.handle_event", span_attributes) as span:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...............................F
=================================== FAILURES ===================================
____________________ test_handle_event_records_delivery_id _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x72963b654bb0>

    @pytest.mark.asyncio
    async def test_handle_event_records_delivery_id(webhook_handler):
        """Test the GitHub delivery GUID is attached to the event span."""
        with patch("stampbot.webhook_handler.create_span") as mock_span:
            mock_span.return_value.__enter__ = MagicMock(return_value=None)
            mock_span.return_value.__exit__ = MagicMock(return_value=False)
    
            await webhook_handler.handle_event("ping", {"zen": "x"}, delivery_id="abc-123")
    
        attributes = mock_span.call_args_list[0].args[1]
>       assert attributes["github.delivery_id"] == "abc-123"
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       KeyError: 'github.delivery_id'

tests/test_webhook_handler.py:444: KeyError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:29:13.236728Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:29:13.236728Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_handle_event_records_delivery_id - KeyError: 'github.delivery_id'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 391 passed, 3 deselected, 1 warning in 2.47s
operator: core/AddNot, occurrence: 5, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -160,7 +160,7 @@
             )
 
             # Route to appropriate handler
-            if event_type == "pull_request":
+            if not event_type == "pull_request":
                 result = await self._handle_pull_request(payload)
             elif event_type == "pull_request_review_comment":
                 result = await self._handle_pr_comment(payload)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x738116f77240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ok"
E       AssertionError: assert 'error' == 'ok'
E         
E         - ok
E         + error

tests/test_main.py:367: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:39:46.157100Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "Missing required fields in PR event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:39:46.157262Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:39:46.157753Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:39:46.157100Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:212 {'event': 'Missing required fields in PR event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:39:46.157262Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - AssertionError: assert 'error' == 'ok'
  
  - ok
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.10s
operator: core/AddNot, occurrence: 6, definition_name: handle_event
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -166,7 +166,7 @@
                 result = await self._handle_pr_comment(payload)
             elif event_type == "issue_comment":
                 # Issue comments can be on PRs too
-                if "pull_request" in payload.get("issue", {}):
+                if not "pull_request" in payload.get("issue", {}):
                     result = await self._handle_pr_comment(payload)
                 else:
                     result = {"status": "ignored", "message": "Not a PR comment"}
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7cb836961d90>
mock_github_client = <MagicMock name='github_client' id='137130649132336'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:17:55.271278Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:17:55.271278Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.44s
operator: core/AddNot, occurrence: 7, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -208,7 +208,7 @@
                 "github.action": action or "unknown",
             },
         ) as span:
-            if not all([pr_number, repo_full_name, installation_id]):
+            if not not all([pr_number, repo_full_name, installation_id]):
                 logger.warning("Missing required fields in PR event")
                 add_span_attributes(span, {"webhook.result": "missing_fields"})
                 set_span_ok(span)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7def0c11d0f0>
mock_github_client = <MagicMock name='github_client' id='138465675171152'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:10:29.868546Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "Missing required fields in PR event", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:10:29.868666Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:10:29.868546Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:212 {'event': 'Missing required fields in PR event', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:10:29.868666Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.38s
operator: core/AddNot, occurrence: 8, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -214,7 +214,7 @@
                 set_span_ok(span)
                 return {"status": "error", "message": "Missing required fields"}
 
-            if action not in PULL_REQUEST_ACTIONS:
+            if not action not in PULL_REQUEST_ACTIONS:
                 # Edits, review requests, closes, and similar actions never change
                 # review state, so they must not cost a policy read.
                 add_span_attributes(span, {"webhook.result": "action_ignored"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7611f6b590f0>
mock_github_client = <MagicMock name='github_client' id='129819546309968'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:31:36.972035Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:31:36.972035Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.41s
operator: core/AddNot, occurrence: 9, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -229,7 +229,7 @@
                 owner_type,
             )
 
-            if repo_config.config_error:
+            if not repo_config.config_error:
                 logger.warning(
                     "Invalid Stampbot configuration for %s: %s",
                     repo_full_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a4418e0d0f0>
mock_github_client = <MagicMock name='github_client' id='134432917116240'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:54:14.143945Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:54:14.144630Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "error": null}, "event": "Invalid Stampbot configuration for octocat/hello-world: None", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:54:14.144731Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:54:14.143945Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:54:14.144630Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:233 {'extra': {'repo': 'octocat/hello-world', 'error': None}, 'event': 'Invalid Stampbot configuration for octocat/hello-world: None', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:54:14.144731Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.42s
operator: core/AddNot, occurrence: 10, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -244,7 +244,7 @@
                     },
                 )
 
-                if action == "opened":
+                if not action == "opened":
                     await run_in_threadpool(
                         github_client.create_pr_review_comment,
                         installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
..................F
=================================== FAILURES ===================================
_____ test_missing_repo_config_with_invalid_service_defaults_fails_closed ______

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x71d56a210440>
mock_github_client = <MagicMock name='github_client' id='125161420132704'>

    @pytest.mark.asyncio
    async def test_missing_repo_config_with_invalid_service_defaults_fails_closed(
        webhook_handler, mock_github_client
    ):
        """Test invalid service defaults block a PR when no repo config exists."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        service_defaults = MagicMock(spec=["approve_commands"])
        service_defaults.approve_commands = "approve"
    
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = service_defaults
            mock_settings.defaults = service_defaults
    
            result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result == {"status": "error", "message": "Invalid repository configuration"}
>       review_body = mock_github_client.create_pr_review_comment.call_args.args[3]
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AttributeError: 'NoneType' object has no attribute 'args'

tests/test_webhook_handler.py:1292: AttributeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:55:51.859419Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid service default configuration: approve_commands must be a list of strings"}, "event": "Invalid service default configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings", "logger": "stampbot.repo_policy", "level": "warning", "timestamp": "2026-11-04T03:55:51.859886Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "warning"}
{"extra": {"repo": "octocat/hello-world", "error": "Invalid service default configuration: approve_commands must be a list of strings"}, "event": "Invalid Stampbot configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:55:51.859972Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:55:51.859419Z'}
WARNING  stampbot.repo_policy:repo_policy.py:142 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid service default configuration: approve_commands must be a list of strings'}, 'event': 'Invalid service default configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings', 'logger': 'stampbot.repo_policy', 'level': 'warning', 'timestamp': '2026-11-04T03:55:51.859886Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:233 {'extra': {'repo': 'octocat/hello-world', 'error': 'Invalid service default configuration: approve_commands must be a list of strings'}, 'event': 'Invalid Stampbot configuration for octocat/hello-world: Invalid service default configuration: approve_commands must be a list of strings', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:55:51.859972Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_missing_repo_config_with_invalid_service_defaults_fails_closed - AttributeError: 'NoneType' object has no attribute 'args'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 450 passed, 3 deselected, 1 warning in 3.32s
operator: core/AddNot, occurrence: 11, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -275,7 +275,7 @@
             )
 
             decision, label = self._decide(event)
-            if decision is Decision.APPROVE and label:
+            if not decision is Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7361737590f0>
mock_github_client = <MagicMock name='github_client' id='126862406899024'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:41:13.614246Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:41:13.614882Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:41:13.614246Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:41:13.614882Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.42s
operator: core/AddNot, occurrence: 12, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -286,7 +286,7 @@
                 set_span_ok(span)
                 result = dict(NO_ACTION)
 
-            if action == "opened":
+            if not action == "opened":
                 # This only produces an operator warning, so it runs after the
                 # review decision instead of delaying it.
                 await self._warn_missing_approval_labels(event)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7af69fe67e70>
mock_github_client = <MagicMock name='github_client' id='135199752734096'>

    @pytest.mark.asyncio
    async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
        """Test missing approval label triggers label check after the review decision."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        mock_github_client.repo_has_label.return_value = False
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        # The label on the pull request exists by definition; only "stamp" is checked.
>       mock_github_client.repo_has_label.assert_called_once_with(12345, "octocat/hello-world", "stamp")

tests/test_webhook_handler.py:352: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.repo_has_label' id='135199752216192'>
args = (12345, 'octocat/hello-world', 'stamp'), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:40:51.290091Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:40:51.290537Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:40:51.290718Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:40:51.290091Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:40:51.290537Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:40:51.290718Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 382 passed, 3 deselected, 1 warning in 2.55s
operator: core/AddNot, occurrence: 13, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -303,7 +303,7 @@
             The decision and the approval label it concerns, if any.
         """
         config = event.repo_config
-        if not config.auto_approve_on_label:
+        if not not config.auto_approve_on_label:
             return Decision.NONE, None
 
         if event.action == "unlabeled":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f69c4dc90f0>
mock_github_client = <MagicMock name='github_client' id='140092272465232'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:14:44.651780Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:14:44.652689Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:14:44.651780Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:14:44.652689Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.39s
operator: core/AddNot, occurrence: 14, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -306,7 +306,7 @@
         if not config.auto_approve_on_label:
             return Decision.NONE, None
 
-        if event.action == "unlabeled":
+        if not event.action == "unlabeled":
             if event.event_label in config.approval_labels:
                 return Decision.DISMISS, event.event_label
             return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7920ac44d0f0>
mock_github_client = <MagicMock name='github_client' id='133181259175248'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:01:22.921333Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:01:22.922091Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:01:22.921333Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:01:22.922091Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.37s
operator: core/AddNot, occurrence: 15, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -307,7 +307,7 @@
             return Decision.NONE, None
 
         if event.action == "unlabeled":
-            if event.event_label in config.approval_labels:
+            if not event.event_label in config.approval_labels:
                 return Decision.DISMISS, event.event_label
             return Decision.NONE, None
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7fe47ed2a4e0>
mock_github_client = <MagicMock name='github_client' id='140619385079184'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "error"
E       AssertionError: assert 'ignored' == 'error'
E         
E         - error
E         + ignored

tests/test_webhook_handler.py:210: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:10:06.545133Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:10:06.545569Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:10:06.545133Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:10:06.545569Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - AssertionError: assert 'ignored' == 'error'
  
  - error
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.45s
operator: core/AddNot, occurrence: 16, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -312,7 +312,7 @@
             return Decision.NONE, None
 
         label = self._find_approval_label(event.labels, config)
-        if label is None:
+        if not label is None:
             return Decision.NONE, None
 
         if event.action in ("opened", "reopened"):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x78db86f750f0>
mock_github_client = <MagicMock name='github_client' id='132884279448912'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:17:38.574830Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:17:38.575725Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:17:38.574830Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:17:38.575725Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.38s
operator: core/AddNot, occurrence: 17, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -315,7 +315,7 @@
         if label is None:
             return Decision.NONE, None
 
-        if event.action in ("opened", "reopened"):
+        if not event.action in ("opened", "reopened"):
             # GitHub also sends a labeled event for every label present when a
             # pull request is created. Both events approve, because a missed
             # approval costs more than a duplicate one; _approve_pr dismisses
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a629465d0f0>
mock_github_client = <MagicMock name='github_client' id='134563835827536'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:45:27.737111Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:45:27.738066Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:45:27.737111Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:45:27.738066Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.47s
operator: core/AddNot, occurrence: 18, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -322,7 +322,7 @@
             # any duplicate afterwards.
             return Decision.APPROVE, label
 
-        if event.action == "labeled":
+        if not event.action == "labeled":
             if event.event_label in config.approval_labels:
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d88cd15a520>
mock_github_client = <MagicMock name='github_client' id='138026531798528'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
>       assert [r["status"] for r in results] == ["success", "success"]
E       AssertionError: assert ['success', 'ignored'] == ['success', 'success']
E         
E         At index 1 diff: 'ignored' != 'success'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E               'success',
E         -     'success',
E         +     'ignored',
E           ]

tests/test_webhook_handler.py:116: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:10:14.971665Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:10:14.972142Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:10:14.972369Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:10:14.972898Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:10:14.971665Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:10:14.972142Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:10:14.972369Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:10:14.972898Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: assert ['success', 'ignored'] == ['success', 'success']
  
  At index 1 diff: 'ignored' != 'success'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        'success',
  -     'success',
  +     'ignored',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.36s
operator: core/AddNot, occurrence: 19, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -323,7 +323,7 @@
             return Decision.APPROVE, label
 
         if event.action == "labeled":
-            if event.event_label in config.approval_labels:
+            if not event.event_label in config.approval_labels:
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75d3a8b1e520>
mock_github_client = <MagicMock name='github_client' id='129551949757952'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
>       assert [r["status"] for r in results] == ["success", "success"]
E       AssertionError: assert ['success', 'ignored'] == ['success', 'success']
E         
E         At index 1 diff: 'ignored' != 'success'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E               'success',
E         -     'success',
E         +     'ignored',
E           ]

tests/test_webhook_handler.py:116: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:10:03.734228Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:10:03.734716Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:10:03.734922Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:10:03.735450Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:10:03.734228Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:10:03.734716Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:10:03.734922Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:10:03.735450Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: assert ['success', 'ignored'] == ['success', 'success']
  
  At index 1 diff: 'ignored' != 'success'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        'success',
  -     'success',
  +     'ignored',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.38s
operator: core/AddNot, occurrence: 20, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -327,7 +327,7 @@
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
 
-        if event.action == "synchronize" and config.reapprove:
+        if not event.action == "synchronize" and config.reapprove:
             return Decision.REFRESH, label
 
         return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7bab64ee47d0>
mock_github_client = <MagicMock name='github_client' id='135976084796288'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:198: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:31:18.857953Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:31:18.857953Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.44s
operator: core/AddNot, occurrence: 21, definition_name: _find_approval_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -343,7 +343,7 @@
             Matching approval label, if present.
         """
         for label in repo_config.approval_labels:
-            if label in labels:
+            if not label in labels:
                 return label
         return None
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7de4ad87d0f0>
mock_github_client = <MagicMock name='github_client' id='138421139182928'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        assert "approved" in result["message"].lower()
>       mock_github_client.approve_pr.assert_called_once_with(
            12345,  # installation_id
            "octocat/hello-world",  # repo
            42,  # pr_number
            "Auto-approved by Stampbot (label: autoapprove)",
        )

tests/test_webhook_handler.py:79: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='138421139182256'>
args = (12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')
kwargs = {}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: approve_pr(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')
E             Actual: approve_pr(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: stamp)')

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:37:24.108363Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:37:24.109056Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "stamp"}, "event": "PR #42 has approval label: stamp", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:37:24.109311Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:37:24.108363Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:37:24.109056Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'stamp'}, 'event': 'PR #42 has approval label: stamp', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:37:24.109311Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: expected call not found.
Expected: approve_pr(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')
  Actual: approve_pr(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: stamp)')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.45s
operator: core/AddNot, occurrence: 22, definition_name: _refresh_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -366,7 +366,7 @@
             event.repo_full_name,
             event.pr_number,
         )
-        if reviews is None:
+        if not reviews is None:
             # GitHub could not be read. Approving may create a duplicate that
             # the cleanup removes; skipping could miss an approval for good.
             logger.warning(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......F
=================================== FAILURES ===================================
___________ test_pr_synchronize_unknown_review_state_still_approves ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74067ed307d0>
mock_github_client = <MagicMock name='github_client' id='127571274216320'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_unknown_review_state_still_approves(
        webhook_handler, mock_github_client
    ):
        """Test a failed listing during a refresh approves instead of skipping."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
        mock_github_client.get_repo_file.return_value = "reapprove = true"
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:198: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:43:19.962671Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:43:19.962671Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_unknown_review_state_still_approves - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 367 passed, 3 deselected, 1 warning in 2.43s
operator: core/AddNot, occurrence: 23, definition_name: _approve_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -404,7 +404,7 @@
         pr_author = event.pr.get("user", {}).get("login", "")
 
         author_team_slugs: list[str] | None = None
-        if config.needs_team_check(pr_author) and event.owner_login:
+        if not config.needs_team_check(pr_author) and event.owner_login:
             author_team_slugs = await run_in_threadpool(
                 github_client.get_user_team_slugs,
                 event.installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
.........................................F
=================================== FAILURES ===================================
________________ test_pr_with_allowed_teams_triggers_team_check ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ab3d8c81630>
mock_github_client = <MagicMock name='github_client' id='134912849139696'>

    @pytest.mark.asyncio
    async def test_pr_with_allowed_teams_triggers_team_check(webhook_handler, mock_github_client):
        """Test PR with allowed_teams configured triggers team membership check."""
        payload = load_fixture("pr_labeled_autoapprove")
        # Config with allowed_teams - user not in allowed_users so needs team check
        mock_github_client.get_repo_file.return_value = """
    allowed_teams = ["acme/release-team"]
    """
        # User is in the allowed team
        mock_github_client.get_user_team_slugs.return_value = ["release-team"]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:1714: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:41:02.952745Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "pr_author": "contributor", "reason": "PR author not a member of any allowed team"}, "event": "PR #42 not eligible for auto-approval: PR author not a member of any allowed team", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:41:02.953321Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:41:02.952745Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:424 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'pr_author': 'contributor', 'reason': 'PR author not a member of any allowed team'}, 'event': 'PR #42 not eligible for auto-approval: PR author not a member of any allowed team', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:41:02.953321Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_with_allowed_teams_triggers_team_check - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 473 passed, 3 deselected, 1 warning in 3.40s
operator: core/AddNot, occurrence: 24, definition_name: _approve_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -420,7 +420,7 @@
             pr_author,
             author_team_slugs,
         )
-        if not is_eligible:
+        if not not is_eligible:
             logger.info(
                 "PR #%d not eligible for auto-approval: %s",
                 event.pr_number,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f8eefb3d0f0>
mock_github_client = <MagicMock name='github_client' id='140251974538576'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:21:57.948452Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:21:57.949109Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "pr_author": "contributor", "reason": null}, "event": "PR #42 not eligible for auto-approval: None", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:21:57.949356Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:21:57.948452Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:21:57.949109Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:424 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'pr_author': 'contributor', 'reason': None}, 'event': 'PR #42 not eligible for auto-approval: None', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:21:57.949356Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.38s
operator: core/AddNot, occurrence: 25, definition_name: _approve_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -459,7 +459,7 @@
         add_span_attributes(
             span,
             {
-                "webhook.result": "approved" if success else "approval_failed",
+                "webhook.result": "approved" if not success else "approval_failed",
                 "webhook.trigger_label": label,
             },
         )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/AddNot, occurrence: 26, definition_name: _approve_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -466,7 +466,7 @@
         set_span_ok(span)
 
         return {
-            "status": "success" if success else "error",
+            "status": "success" if not success else "error",
             "message": f"PR approved via label: {label}" if success else "Failed to approve PR",
         }
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77903b3c10f0>
mock_github_client = <MagicMock name='github_client' id='131461374285136'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:03:04.451509Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:03:04.452119Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:03:04.452370Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:03:04.451509Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:03:04.452119Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:03:04.452370Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.36s
operator: core/AddNot, occurrence: 27, definition_name: _approve_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -467,7 +467,7 @@
 
         return {
             "status": "success" if success else "error",
-            "message": f"PR approved via label: {label}" if success else "Failed to approve PR",
+            "message": f"PR approved via label: {label}" if not success else "Failed to approve PR",
         }
 
     async def _dismiss_for_label(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ba06d9090f0>
mock_github_client = <MagicMock name='github_client' id='135928985277776'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
>       assert "approved" in result["message"].lower()
E       AssertionError: assert 'approved' in 'failed to approve pr'
E        +  where 'failed to approve pr' = <built-in method lower of str object at 0x7ba06de33cf0>()
E        +    where <built-in method lower of str object at 0x7ba06de33cf0> = 'Failed to approve PR'.lower

tests/test_webhook_handler.py:78: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:21:03.441955Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:21:03.442602Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:21:03.442951Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:21:03.441955Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:21:03.442602Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:21:03.442951Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'approved' in 'failed to approve pr'
 +  where 'failed to approve pr' = <built-in method lower of str object at 0x7ba06de33cf0>()
 +    where <built-in method lower of str object at 0x7ba06de33cf0> = 'Failed to approve PR'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.40s
operator: core/AddNot, occurrence: 28, definition_name: _dismiss_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -501,7 +501,7 @@
         add_span_attributes(
             span,
             {
-                "webhook.result": "dismissed" if success else "dismiss_failed",
+                "webhook.result": "dismissed" if not success else "dismiss_failed",
                 "webhook.removed_label": label,
             },
         )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/AddNot, occurrence: 29, definition_name: _dismiss_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -508,7 +508,7 @@
         set_span_ok(span)
 
         return {
-            "status": "success" if success else "error",
+            "status": "success" if not success else "error",
             "message": "Approvals dismissed" if success else "Failed to dismiss approvals",
         }
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7da12bad6030>
mock_github_client = <MagicMock name='github_client' id='138131198117264'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "error"
E       AssertionError: assert 'success' == 'error'
E         
E         - error
E         + success

tests/test_webhook_handler.py:210: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:37.981559Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:09:37.981967Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:37.982061Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "Stampbot reviews could not be listed"}, "event": "Error dismissing approvals: Stampbot reviews could not be listed", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T04:09:37.982260Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:37.981559Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:09:37.981967Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:37.982061Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': 'Stampbot reviews could not be listed'}, 'event': 'Error dismissing approvals: Stampbot reviews could not be listed', 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T04:09:37.982260Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - AssertionError: assert 'success' == 'error'
  
  - error
  + success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.43s
operator: core/AddNot, occurrence: 30, definition_name: _dismiss_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -509,7 +509,7 @@
 
         return {
             "status": "success" if success else "error",
-            "message": "Approvals dismissed" if success else "Failed to dismiss approvals",
+            "message": "Approvals dismissed" if not success else "Failed to dismiss approvals",
         }
 
     async def _warn_missing_approval_labels(self, event: PullRequestEvent) -> None:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......................................................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7e33f1958c90>
mock_github_client = <MagicMock name='github_client' id='138761581215232'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
        """Test removing autoapprove label dismisses approvals."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"},
            {"id": 222, "state": "APPROVED", "commit_id": "abc123def456"},
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
>       assert "dismissed" in result["message"].lower()
E       AssertionError: assert 'dismissed' in 'failed to dismiss approvals'
E        +  where 'failed to dismiss approvals' = <built-in method lower of str object at 0x7e33f1dfa290>()
E        +    where <built-in method lower of str object at 0x7e33f1dfa290> = 'Failed to dismiss approvals'.lower

tests/test_webhook_handler.py:675: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:38:08.783729Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:38:08.784131Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:38:08.784249Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:38:08.783729Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:38:08.784131Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:38:08.784249Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'dismissed' in 'failed to dismiss approvals'
 +  where 'failed to dismiss approvals' = <built-in method lower of str object at 0x7e33f1dfa290>()
 +    where <built-in method lower of str object at 0x7e33f1dfa290> = 'Failed to dismiss approvals'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 415 passed, 3 deselected, 1 warning in 2.49s
operator: core/AddNot, occurrence: 31, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -523,7 +523,7 @@
         """
         present = event.labels
         for label in event.repo_config.approval_labels:
-            if label in present:
+            if not label in present:
                 continue
             label_exists = await run_in_threadpool(
                 github_client.repo_has_label,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x758611043e70>
mock_github_client = <MagicMock name='github_client' id='129218691286416'>

    @pytest.mark.asyncio
    async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
        """Test missing approval label triggers label check after the review decision."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        mock_github_client.repo_has_label.return_value = False
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        # The label on the pull request exists by definition; only "stamp" is checked.
>       mock_github_client.repo_has_label.assert_called_once_with(12345, "octocat/hello-world", "stamp")

tests/test_webhook_handler.py:352: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.repo_has_label' id='129218692865664'>
args = (12345, 'octocat/hello-world', 'stamp'), kwargs = {}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: repo_has_label(12345, 'octocat/hello-world', 'stamp')
E             Actual: repo_has_label(12345, 'octocat/hello-world', 'autoapprove')

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:52:17.418417Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:52:17.418824Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:52:17.419008Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "label": "autoapprove"}, "event": "Approval label autoapprove not found in octocat/hello-world", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:52:17.419472Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:52:17.418417Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:52:17.418824Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:52:17.419008Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:535 {'extra': {'repo': 'octocat/hello-world', 'label': 'autoapprove'}, 'event': 'Approval label autoapprove not found in octocat/hello-world', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:52:17.419472Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: expected call not found.
Expected: repo_has_label(12345, 'octocat/hello-world', 'stamp')
  Actual: repo_has_label(12345, 'octocat/hello-world', 'autoapprove')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 382 passed, 3 deselected, 1 warning in 2.48s
operator: core/AddNot, occurrence: 32, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -531,7 +531,7 @@
                 event.repo_full_name,
                 label,
             )
-            if label_exists is False:
+            if not label_exists is False:
                 logger.warning(
                     "Approval label %s not found in %s",
                     label,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.31s
operator: core/AddNot, occurrence: 33, definition_name: _approval_needs_refresh
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -553,7 +553,7 @@
         Returns:
             True when a prior approval exists but no active approval applies to the current head.
         """
-        if not reviews:
+        if not not reviews:
             return False
 
         return not self._has_current_active_approval(reviews, head_sha)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...................................F
=================================== FAILURES ===================================
_________ test_pr_labeled_unrelated_label_with_approval_label_ignored __________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x71b06e93ff50>
mock_github_client = <MagicMock name='github_client' id='125002603119344'>

    @pytest.mark.asyncio
    async def test_pr_labeled_unrelated_label_with_approval_label_ignored(
        webhook_handler, mock_github_client
    ):
        """Test unrelated label changes do not re-approve just because an approval label exists."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["label"] = {"name": "bug"}
        payload["pull_request"]["labels"].append({"name": "bug"})
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "ignored"
E       AssertionError: assert 'success' == 'ignored'
E         
E         - ignored
E         + success

tests/test_webhook_handler.py:496: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:49:22.857390Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:49:22.857821Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:49:22.858111Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:49:22.857390Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:49:22.857821Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:49:22.858111Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_unrelated_label_with_approval_label_ignored - AssertionError: assert 'success' == 'ignored'
  
  - ignored
  + success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 395 passed, 3 deselected, 1 warning in 2.54s
operator: core/AddNot, occurrence: 34, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -585,7 +585,7 @@
 
         def covers_head(review: BotReview) -> bool:
             commit = review.get("commit_id")
-            if proven_only:
+            if not proven_only:
                 return bool(head_sha) and commit == head_sha
             return not head_sha or not commit or commit == head_sha
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.............F
=================================== FAILURES ===================================
___________ test_active_approvals_for_head[reviews4-None-expected4] ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x790f926f6690>
reviews = [{'id': 1, 'state': 'APPROVED', 'commit_id': None}, {'id': 2, 'state': 'APPROVED', 'commit_id': 'head'}]
head_sha = None, expected = [1, 2]

    @pytest.mark.parametrize(
        ("reviews", "head_sha", "expected"),
        [
            ([], "head", []),
            ([{"id": 1, "state": "APPROVED", "commit_id": "head"}], "head", [1]),
            (
                [
                    {"id": 3, "state": "APPROVED", "commit_id": "head"},
                    {"id": 1, "state": "APPROVED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [1, 2, 3],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": "old"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": None},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                None,
                [1, 2],
            ),
            (
                [
                    {"id": 1, "state": "DISMISSED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
        ],
    )
    def test_active_approvals_for_head(webhook_handler, reviews, head_sha, expected):
        """Test which approvals count as covering the head, oldest first."""
>       assert webhook_handler._active_approvals_for_head(reviews, head_sha) == expected
E       assert [] == [1, 2]
E         
E         Right contains 2 more items, first extra item: 1
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + []
E         - [
E         -     1,
E         -     2,
E         - ]

tests/test_webhook_handler.py:256: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_active_approvals_for_head[reviews4-None-expected4] - assert [] == [1, 2]
  
  Right contains 2 more items, first extra item: 1
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     1,
  -     2,
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 373 passed, 3 deselected, 1 warning in 2.41s
operator: core/AddNot, occurrence: 35, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -624,7 +624,7 @@
         comment_body = comment.get("body", "")
 
         # Security: limit comment length to prevent DoS
-        if len(comment_body) > MAX_COMMENT_LENGTH:
+        if not len(comment_body) > MAX_COMMENT_LENGTH:
             return {"status": "ignored", "message": "Comment too long"}
 
         comment_body = comment_body.lower().strip()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ad3873f9d90>
mock_github_client = <MagicMock name='github_client' id='135048943607088'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:22:39.878785Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:22:39.878785Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.42s
operator: core/AddNot, occurrence: 36, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -630,7 +630,7 @@
         comment_body = comment_body.lower().strip()
 
         # Check if bot is mentioned
-        if "@stampbot" not in comment_body:
+        if not "@stampbot" not in comment_body:
             return {"status": "ignored", "message": "Bot not mentioned"}
 
         # Extract PR info
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x79bc79ff9d90>
mock_github_client = <MagicMock name='github_client' id='133850425449776'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:38:20.456914Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:38:20.456914Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.44s
operator: core/AddNot, occurrence: 37, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -634,7 +634,7 @@
             return {"status": "ignored", "message": "Bot not mentioned"}
 
         # Extract PR info
-        if "pull_request" in payload.get("issue", {}):
+        if not "pull_request" in payload.get("issue", {}):
             pr_url = payload["issue"]["pull_request"]["url"]
             # Extract PR number from URL
             pr_number = int(pr_url.split("/")[-1])
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x71e8f8165010>
mock_github_client = <MagicMock name='github_client' id='125245427883312'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:15:17.337028Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:15:17.337028Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.44s
operator: core/AddNot, occurrence: 38, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -660,7 +660,7 @@
                 "chatops.commenter": commenter,
             },
         ) as span:
-            if not all([pr_number, repo_full_name, installation_id]):
+            if not not all([pr_number, repo_full_name, installation_id]):
                 logger.warning("Missing required fields in comment event")
                 add_span_attributes(span, {"chatops.result": "missing_fields"})
                 set_span_ok(span)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7a5fe0375d90>
mock_github_client = <MagicMock name='github_client' id='134552221588784'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:08.565127Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "Missing required fields in comment event", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:42:08.565263Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:08.565127Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:664 {'event': 'Missing required fields in comment event', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:42:08.565263Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.42s
operator: core/AddNot, occurrence: 39, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -674,7 +674,7 @@
                 owner_type,
             )
 
-            if repo_config.config_error:
+            if not repo_config.config_error:
                 add_span_attributes(
                     span,
                     {
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x753f8bd75d90>
mock_github_client = <MagicMock name='github_client' id='128915877076272'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:29:48.477570Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:29:48.477995Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:29:48.477570Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:29:48.477995Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.45s
operator: core/AddNot, occurrence: 40, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -688,7 +688,7 @@
                     "message": "Invalid repository configuration",
                 }
 
-            if not repo_config.chatops_enabled:
+            if not not repo_config.chatops_enabled:
                 add_span_attributes(span, {"chatops.result": "disabled"})
                 set_span_ok(span)
                 return {"status": "ignored", "message": "Chatops not enabled"}
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ad5adb61d90>
mock_github_client = <MagicMock name='github_client' id='135058180431152'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:34:07.397126Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:34:07.397566Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:34:07.397126Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:34:07.397566Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.41s
operator: core/AddNot, occurrence: 41, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -695,7 +695,7 @@
 
             # Parse command
             command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
-            if not command_match:
+            if not not command_match:
                 chatops_commands_total.labels(command="none", status="ignored").inc()
                 add_span_attributes(span, {"chatops.result": "no_command"})
                 set_span_ok(span)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7765abd81d90>
mock_github_client = <MagicMock name='github_client' id='131278575770928'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:33:46.493735Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:33:46.494146Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:33:46.493735Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:33:46.494146Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.44s
operator: core/AddNot, occurrence: 42, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -704,7 +704,7 @@
             command = command_match.group(1).lower()
             add_span_attributes(span, {"chatops.command": command})
 
-            if command == "help":
+            if not command == "help":
                 success = await self._post_help(
                     installation_id,
                     repo_full_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.................................................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74e273ebb0e0>
mock_github_client = <MagicMock name='github_client' id='128516047216928'>

    @pytest.mark.asyncio
    async def test_issue_comment_approve(webhook_handler, mock_github_client):
        """Test @stampbot approve command."""
        payload = load_fixture("issue_comment_approve")
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
>       assert "approved" in result["message"].lower()
E       AssertionError: assert 'approved' in 'help message posted'
E        +  where 'help message posted' = <built-in method lower of str object at 0x74e278431430>()
E        +    where <built-in method lower of str object at 0x74e278431430> = 'Help message posted'.lower

tests/test_webhook_handler.py:830: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:48:16.952827Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:48:16.953339Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:48:16.952827Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:48:16.953339Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'approved' in 'help message posted'
 +  where 'help message posted' = <built-in method lower of str object at 0x74e278431430>()
 +    where <built-in method lower of str object at 0x74e278431430> = 'Help message posted'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 425 passed, 3 deselected, 1 warning in 2.93s
operator: core/AddNot, occurrence: 43, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -714,7 +714,7 @@
 
                 chatops_commands_total.labels(
                     command="help",
-                    status="success" if success else "failure",
+                    status="success" if not success else "failure",
                 ).inc()
 
                 add_span_attributes(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/AddNot, occurrence: 44, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -718,7 +718,7 @@
                 ).inc()
 
                 add_span_attributes(
-                    span, {"chatops.result": "help_posted" if success else "help_failed"}
+                    span, {"chatops.result": "help_posted" if not success else "help_failed"}
                 )
                 set_span_ok(span)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/AddNot, occurrence: 45, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -723,7 +723,7 @@
                 set_span_ok(span)
 
                 return {
-                    "status": "success" if success else "error",
+                    "status": "success" if not success else "error",
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
..F
=================================== FAILURES ===================================
_______________________ test_issue_comment_help_command ________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77553443e740>
mock_github_client = <MagicMock name='github_client' id='131207856417360'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_command(webhook_handler, mock_github_client):
        """Test @stampbot help posts contextual help."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:974: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:19:25.438492Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:19:25.438910Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:19:25.438492Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:19:25.438910Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_command - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 434 passed, 3 deselected, 1 warning in 2.89s
operator: core/AddNot, occurrence: 46, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -724,7 +724,7 @@
 
                 return {
                     "status": "success" if success else "error",
-                    "message": "Help message posted" if success else "Failed to post help message",
+                    "message": "Help message posted" if not success else "Failed to post help message",
                 }
 
             if command in repo_config.approve_commands + repo_config.unapprove_commands:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/AddNot, occurrence: 47, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -727,7 +727,7 @@
                     "message": "Help message posted" if success else "Failed to post help message",
                 }
 
-            if command in repo_config.approve_commands + repo_config.unapprove_commands:
+            if not command in repo_config.approve_commands + repo_config.unapprove_commands:
                 has_permission = await run_in_threadpool(
                     github_client.user_has_permission,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.................................................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x78decb4d6f90>
mock_github_client = <MagicMock name='github_client' id='132898310709200'>

    @pytest.mark.asyncio
    async def test_issue_comment_approve(webhook_handler, mock_github_client):
        """Test @stampbot approve command."""
        payload = load_fixture("issue_comment_approve")
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
        assert "approved" in result["message"].lower()
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.user_has_permission.assert_called_once()

tests/test_webhook_handler.py:832: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.user_has_permission' id='132898310704160'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'user_has_permission' to have been called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:17:51.650830Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:17:51.651301Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:17:51.650830Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:17:51.651301Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: Expected 'user_has_permission' to have been called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 425 passed, 3 deselected, 1 warning in 2.91s
operator: core/AddNot, occurrence: 48, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -735,7 +735,7 @@
                     commenter,
                     repo_config.chatops_required_permission,
                 )
-                if not has_permission:
+                if not not has_permission:
                     chatops_commands_total.labels(command=command, status="forbidden").inc()
                     add_span_attributes(
                         span,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c4dc897dd90>
mock_github_client = <MagicMock name='github_client' id='136673538268464'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:35:26.487120Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:35:26.487605Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:35:26.487120Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:35:26.487605Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.47s
operator: core/AddNot, occurrence: 49, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -753,7 +753,7 @@
                     }
 
             # Handle approve commands
-            if command in repo_config.approve_commands:
+            if not command in repo_config.approve_commands:
                 head_sha = await run_in_threadpool(
                     github_client.get_pr_head_sha,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74e617af9520>
mock_github_client = <MagicMock name='github_client' id='128531606330672'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:49:26.560253Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:49:26.560722Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:49:26.560253Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:49:26.560722Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.49s
operator: core/AddNot, occurrence: 50, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -771,7 +771,7 @@
 
                 chatops_commands_total.labels(
                     command="approve",
-                    status="success" if success else "failure",
+                    status="success" if not success else "failure",
                 ).inc()
 
                 add_span_attributes(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.41s
operator: core/AddNot, occurrence: 51, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -775,7 +775,7 @@
                 ).inc()
 
                 add_span_attributes(
-                    span, {"chatops.result": "approved" if success else "approval_failed"}
+                    span, {"chatops.result": "approved" if not success else "approval_failed"}
                 )
                 set_span_ok(span)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/AddNot, occurrence: 52, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -780,7 +780,7 @@
                 set_span_ok(span)
 
                 return {
-                    "status": "success" if success else "error",
+                    "status": "success" if not success else "error",
                     "message": "PR approved" if success else "Failed to approve PR",
                 }
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x702b06e69010>
mock_github_client = <MagicMock name='github_client' id='123330121145648'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:02:24.790966Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:02:24.791406Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:02:24.790966Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:02:24.791406Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.40s
operator: core/AddNot, occurrence: 53, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -781,7 +781,7 @@
 
                 return {
                     "status": "success" if success else "error",
-                    "message": "PR approved" if success else "Failed to approve PR",
+                    "message": "PR approved" if not success else "Failed to approve PR",
                 }
 
             # Handle unapprove commands
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.................................................................F
=================================== FAILURES ===================================
__________________________ test_issue_comment_approve __________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7318585fb2a0>
mock_github_client = <MagicMock name='github_client' id='126548422065328'>

    @pytest.mark.asyncio
    async def test_issue_comment_approve(webhook_handler, mock_github_client):
        """Test @stampbot approve command."""
        payload = load_fixture("issue_comment_approve")
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
>       assert "approved" in result["message"].lower()
E       AssertionError: assert 'approved' in 'failed to approve pr'
E        +  where 'failed to approve pr' = <built-in method lower of str object at 0x73185893f9f0>()
E        +    where <built-in method lower of str object at 0x73185893f9f0> = 'Failed to approve PR'.lower

tests/test_webhook_handler.py:830: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:12:48.239198Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:12:48.239628Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:12:48.239198Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:12:48.239628Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve - AssertionError: assert 'approved' in 'failed to approve pr'
 +  where 'failed to approve pr' = <built-in method lower of str object at 0x73185893f9f0>()
 +    where <built-in method lower of str object at 0x73185893f9f0> = 'Failed to approve PR'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 425 passed, 3 deselected, 1 warning in 2.82s
operator: core/AddNot, occurrence: 54, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -796,7 +796,7 @@
 
                 chatops_commands_total.labels(
                     command="unapprove",
-                    status="success" if success else "failure",
+                    status="success" if not success else "failure",
                 ).inc()
 
                 add_span_attributes(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/AddNot, occurrence: 55, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -800,7 +800,7 @@
                 ).inc()
 
                 add_span_attributes(
-                    span, {"chatops.result": "unapproved" if success else "unapprove_failed"}
+                    span, {"chatops.result": "unapproved" if not success else "unapprove_failed"}
                 )
                 set_span_ok(span)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/AddNot, occurrence: 56, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -805,7 +805,7 @@
                 set_span_ok(span)
 
                 return {
-                    "status": "success" if success else "error",
+                    "status": "success" if not success else "error",
                     "message": "Approvals dismissed" if success else "Failed to dismiss approvals",
                 }
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................................................................F
=================================== FAILURES ===================================
_________________________ test_issue_comment_unapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x752d778930e0>
mock_github_client = <MagicMock name='github_client' id='128838160062112'>

    @pytest.mark.asyncio
    async def test_issue_comment_unapprove(webhook_handler, mock_github_client):
        """Test @stampbot unapprove command."""
        payload = load_fixture("issue_comment_unapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"}
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:921: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:22:47.511858Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:22:47.512291Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:22:47.511858Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:22:47.512291Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_unapprove - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 430 passed, 3 deselected, 1 warning in 2.88s
operator: core/AddNot, occurrence: 57, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -806,7 +806,7 @@
 
                 return {
                     "status": "success" if success else "error",
-                    "message": "Approvals dismissed" if success else "Failed to dismiss approvals",
+                    "message": "Approvals dismissed" if not success else "Failed to dismiss approvals",
                 }
 
             chatops_commands_total.labels(command="unknown", status="ignored").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................................................................F
=================================== FAILURES ===================================
_________________________ test_issue_comment_unapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d5cc5497070>
mock_github_client = <MagicMock name='github_client' id='137837418862240'>

    @pytest.mark.asyncio
    async def test_issue_comment_unapprove(webhook_handler, mock_github_client):
        """Test @stampbot unapprove command."""
        payload = load_fixture("issue_comment_unapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"}
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
>       assert "dismissed" in result["message"].lower()
E       AssertionError: assert 'dismissed' in 'failed to dismiss approvals'
E        +  where 'failed to dismiss approvals' = <built-in method lower of str object at 0x7d5cc58e6150>()
E        +    where <built-in method lower of str object at 0x7d5cc58e6150> = 'Failed to dismiss approvals'.lower

tests/test_webhook_handler.py:922: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:56:51.264976Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:56:51.265416Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:56:51.264976Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:56:51.265416Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_unapprove - AssertionError: assert 'dismissed' in 'failed to dismiss approvals'
 +  where 'failed to dismiss approvals' = <built-in method lower of str object at 0x7d5cc58e6150>()
 +    where <built-in method lower of str object at 0x7d5cc58e6150> = 'Failed to dismiss approvals'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 430 passed, 3 deselected, 1 warning in 2.88s
operator: core/AddNot, occurrence: 58, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -854,7 +854,7 @@
             ]
         )
 
-        if repo_config.auto_approve_on_label:
+        if not repo_config.auto_approve_on_label:
             approval_labels = ", ".join(f"`{label}`" for label in repo_config.approval_labels)
             lines.append(f"Adding any of these labels can trigger approval: {approval_labels}")
         else:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
..F
=================================== FAILURES ===================================
_______________________ test_issue_comment_help_command ________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77de3dc3ec80>
mock_github_client = <MagicMock name='github_client' id='131796426320464'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_command(webhook_handler, mock_github_client):
        """Test @stampbot help posts contextual help."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
        assert "help" in result["message"].lower()
        mock_github_client.user_has_permission.assert_not_called()
        mock_github_client.create_issue_comment.assert_called_once()
        comment_args = mock_github_client.create_issue_comment.call_args[0]
        assert comment_args[:3] == (12345, "octocat/hello-world", 42)
        assert "@stampbot approve" in comment_args[3]
        assert "@stampbot unapprove" in comment_args[3]
>       assert "`autoapprove`" in comment_args[3]
E       AssertionError: assert '`autoapprove`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....Help**: `@stampbot help`\n\n### Label-Based Auto-Approval\n\nLabel-based auto-approval is disabled in this repository.'

tests/test_webhook_handler.py:982: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:44:02.339721Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:44:02.340141Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:44:02.339721Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:44:02.340141Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_command - AssertionError: assert '`autoapprove`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....Help**: `@stampbot help`\n\n### Label-Based Auto-Approval\n\nLabel-based auto-approval is disabled in this repository.'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 434 passed, 3 deselected, 1 warning in 2.90s
operator: core/AddNot, occurrence: 59, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -861,7 +861,7 @@
             lines.append("Label-based auto-approval is disabled in this repository.")
 
         filters = []
-        if repo_config.required_labels:
+        if not repo_config.required_labels:
             filters.append(
                 "required labels: "
                 + ", ".join(f"`{label}`" for label in repo_config.required_labels)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x710c84a76c80>
mock_github_client = <MagicMock name='github_client' id='124298608713008'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
        help_text = mock_github_client.create_issue_comment.call_args[0][3]
        assert "@stampbot approve-it" in help_text
        assert "@stampbot hold-it" in help_text
        assert "`ship-it`" in help_text
>       assert "`dependencies`" in help_text
E       AssertionError: assert '`dependencies`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....ilters apply:\n- required title patterns: `^chore:`\n- allowed users: `renovate[bot]`\n- allowed teams: `release-team`'

tests/test_webhook_handler.py:1007: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:51:54.706274Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:51:54.706274Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - AssertionError: assert '`dependencies`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....ilters apply:\n- required title patterns: `^chore:`\n- allowed users: `renovate[bot]`\n- allowed teams: `release-team`'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.91s
operator: core/AddNot, occurrence: 60, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -866,7 +866,7 @@
                 "required labels: "
                 + ", ".join(f"`{label}`" for label in repo_config.required_labels)
             )
-        if repo_config.required_title_patterns:
+        if not repo_config.required_title_patterns:
             filters.append(
                 "required title patterns: "
                 + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ac897a9c050>
mock_github_client = <MagicMock name='github_client' id='135001950444848'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
        help_text = mock_github_client.create_issue_comment.call_args[0][3]
        assert "@stampbot approve-it" in help_text
        assert "@stampbot hold-it" in help_text
        assert "`ship-it`" in help_text
        assert "`dependencies`" in help_text
>       assert "`^chore:`" in help_text
E       AssertionError: assert '`^chore:`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....l filters apply:\n- required labels: `dependencies`\n- allowed users: `renovate[bot]`\n- allowed teams: `release-team`'

tests/test_webhook_handler.py:1008: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:23:18.135499Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:23:18.135499Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - AssertionError: assert '`^chore:`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....l filters apply:\n- required labels: `dependencies`\n- allowed users: `renovate[bot]`\n- allowed teams: `release-team`'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.90s
operator: core/AddNot, occurrence: 61, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -871,7 +871,7 @@
                 "required title patterns: "
                 + ", ".join(f"`{pattern}`" for pattern in repo_config.required_title_patterns)
             )
-        if repo_config.allowed_users:
+        if not repo_config.allowed_users:
             filters.append(
                 "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73b5cc636c80>
mock_github_client = <MagicMock name='github_client' id='127224677719344'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
        help_text = mock_github_client.create_issue_comment.call_args[0][3]
        assert "@stampbot approve-it" in help_text
        assert "@stampbot hold-it" in help_text
        assert "`ship-it`" in help_text
        assert "`dependencies`" in help_text
        assert "`^chore:`" in help_text
>       assert "`renovate[bot]`" in help_text
E       AssertionError: assert '`renovate[bot]`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....lters apply:\n- required labels: `dependencies`\n- required title patterns: `^chore:`\n- allowed teams: `release-team`'

tests/test_webhook_handler.py:1009: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:29:11.313604Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:29:11.313604Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - AssertionError: assert '`renovate[bot]`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....lters apply:\n- required labels: `dependencies`\n- required title patterns: `^chore:`\n- allowed teams: `release-team`'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.91s
operator: core/AddNot, occurrence: 62, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -875,7 +875,7 @@
             filters.append(
                 "allowed users: " + ", ".join(f"`{user}`" for user in repo_config.allowed_users)
             )
-        if repo_config.allowed_teams:
+        if not repo_config.allowed_teams:
             filters.append(
                 "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c8a51cd8050>
mock_github_client = <MagicMock name='github_client' id='136933542698288'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
        help_text = mock_github_client.create_issue_comment.call_args[0][3]
        assert "@stampbot approve-it" in help_text
        assert "@stampbot hold-it" in help_text
        assert "`ship-it`" in help_text
        assert "`dependencies`" in help_text
        assert "`^chore:`" in help_text
        assert "`renovate[bot]`" in help_text
>       assert "`release-team`" in help_text
E       AssertionError: assert '`release-team`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....ters apply:\n- required labels: `dependencies`\n- required title patterns: `^chore:`\n- allowed users: `renovate[bot]`'

tests/test_webhook_handler.py:1010: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:50:55.835566Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:50:55.835566Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - AssertionError: assert '`release-team`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....ters apply:\n- required labels: `dependencies`\n- required title patterns: `^chore:`\n- allowed users: `renovate[bot]`'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.92s
operator: core/AddNot, occurrence: 63, definition_name: _format_help_message
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -880,7 +880,7 @@
                 "allowed teams: " + ", ".join(f"`{team}`" for team in repo_config.allowed_teams)
             )
 
-        if filters:
+        if not filters:
             lines.extend(["", "Additional approval filters apply:"])
             lines.extend(f"- {filter_text}" for filter_text in filters)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...F
=================================== FAILURES ===================================
_______________ test_issue_comment_help_uses_custom_repo_config ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x78ff12566c80>
mock_github_client = <MagicMock name='github_client' id='133036943105328'>

    @pytest.mark.asyncio
    async def test_issue_comment_help_uses_custom_repo_config(webhook_handler, mock_github_client):
        """Test help reflects custom repo commands, labels, and filters."""
        payload = load_fixture("issue_comment_approve")
        payload["comment"]["body"] = "@stampbot help"
        mock_github_client.get_repo_file.return_value = """
    approval_labels = ["ship-it"]
    approve_commands = ["approve-it"]
    unapprove_commands = ["hold-it"]
    required_labels = ["dependencies"]
    required_title_patterns = ["^chore:"]
    allowed_users = ["renovate[bot]"]
    allowed_teams = ["release-team"]
    """
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
        help_text = mock_github_client.create_issue_comment.call_args[0][3]
        assert "@stampbot approve-it" in help_text
        assert "@stampbot hold-it" in help_text
        assert "`ship-it`" in help_text
>       assert "`dependencies`" in help_text
E       AssertionError: assert '`dependencies`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....elp**: `@stampbot help`\n\n### Label-Based Auto-Approval\n\nAdding any of these labels can trigger approval: `ship-it`'

tests/test_webhook_handler.py:1007: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:19:51.391140Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:19:51.391140Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_help_uses_custom_repo_config - AssertionError: assert '`dependencies`' in '## Stampbot Help\n\n### ChatOps Commands\n\nApprove and unapprove commands require **maintain** permission or higher....elp**: `@stampbot help`\n\n### Label-Based Auto-Approval\n\nAdding any of these labels can trigger approval: `ship-it`'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 435 passed, 3 deselected, 1 warning in 2.86s
operator: core/AddNot, occurrence: 64, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -952,7 +952,7 @@
                 "approval.trigger_type": trigger_type,
             },
         ) as span:
-            if not skip_existing_check:
+            if not not skip_existing_check:
                 reviews = await run_in_threadpool(
                     github_client.find_bot_approval_reviews,
                     installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7160db956520>
mock_github_client = <MagicMock name='github_client' id='124660836777472'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='124660836768064'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:32:44.286637Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:32:44.287109Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:32:44.287333Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:32:44.287751Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:32:44.287920Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:32:44.286637Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:32:44.287109Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:32:44.287333Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:32:44.287751Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:32:44.287920Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.43s
operator: core/AddNot, occurrence: 65, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -959,7 +959,7 @@
                     repo_full_name,
                     pr_number,
                 )
-                if reviews is None:
+                if not reviews is None:
                     # Unknown state. A duplicate is recoverable; a missed
                     # approval is not, so approve and let the cleanup sort it out.
                     logger.warning(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7daacb616520>
mock_github_client = <MagicMock name='github_client' id='138172605495152'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='138172605487424'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:38:45.251732Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:38:45.252216Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:38:45.252415Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42}, "event": "Could not read reviews for PR #42 in octocat/hello-world; approving without a check", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:38:45.252595Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:38:45.253010Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:38:45.253198Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42}, "event": "Could not read reviews for PR #42 in octocat/hello-world; approving without a check", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T03:38:45.253357Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:38:45.251732Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:38:45.252216Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:38:45.252415Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:965 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'Could not read reviews for PR #42 in octocat/hello-world; approving without a check', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:38:45.252595Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:38:45.253010Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:38:45.253198Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:965 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'Could not read reviews for PR #42 in octocat/hello-world; approving without a check', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T03:38:45.253357Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.44s
operator: core/AddNot, occurrence: 66, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -970,7 +970,7 @@
                     )
                     reviews = []
                 existing_approvals = self._active_approvals_for_head(reviews, head_sha)
-                if existing_approvals:
+                if not existing_approvals:
                     logger.info(
                         "PR #%d in %s already has active approval for current head, skipping",
                         pr_number,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7163dd2850f0>
mock_github_client = <MagicMock name='github_client' id='124673747785040'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        assert "approved" in result["message"].lower()
>       mock_github_client.approve_pr.assert_called_once_with(
            12345,  # installation_id
            "octocat/hello-world",  # repo
            42,  # pr_number
            "Auto-approved by Stampbot (label: autoapprove)",
        )

tests/test_webhook_handler.py:79: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='124673747784368'>
args = (12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:55:15.129439Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:55:15.130047Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:55:15.130297Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "existing_review_ids": []}, "event": "PR #42 in octocat/hello-world already has active approval for current head, skipping", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:55:15.130497Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:55:15.129439Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:55:15.130047Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:55:15.130297Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:974 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'existing_review_ids': []}, 'event': 'PR #42 in octocat/hello-world already has active approval for current head, skipping', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:55:15.130497Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: Expected 'approve_pr' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.41s
operator: core/AddNot, occurrence: 67, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1004,7 +1004,7 @@
             duration = time.time() - start_time
             pr_approval_duration_seconds.observe(duration)
 
-            status = "success" if success else "failure"
+            status = "success" if not success else "failure"
             pr_approvals_total.labels(
                 trigger_type=trigger_type,
                 status=status,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.42s
operator: core/AddNot, occurrence: 68, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1010,7 +1010,7 @@
                 status=status,
             ).inc()
 
-            if not success:
+            if not not success:
                 errors_total.labels(error_type="approval_failed").inc()
             else:
                 # Another replica may have approved the same head in the same
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f5aa74abdf0>
mock_github_client = <MagicMock name='github_client' id='140027352342480'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='140027352343152'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:36:31.569772Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:36:31.570203Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:36:31.570395Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:36:31.569772Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:36:31.570203Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:36:31.570395Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.45s
operator: core/AddNot, occurrence: 69, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1075,7 +1075,7 @@
                     repo_full_name,
                     pr_number,
                 )
-                if reviews is None:
+                if not reviews is None:
                     # Nothing can be dismissed without the list, and the
                     # failure has to reach the metric operators watch.
                     raise RuntimeError("Stampbot reviews could not be listed")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7049eb6c3df0>
mock_github_client = <MagicMock name='github_client' id='123462807523344'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='123462807526368'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:24:09.108572Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:24:09.108993Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:24:09.109205Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "Stampbot reviews could not be listed"}, "event": "Error dismissing approvals: Stampbot reviews could not be listed", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T04:24:09.109574Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:24:09.108572Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:24:09.108993Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:24:09.109205Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': 'Stampbot reviews could not be listed'}, 'event': 'Error dismissing approvals: Stampbot reviews could not be listed', 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T04:24:09.109574Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.43s
operator: core/AddNot, occurrence: 70, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1079,7 +1079,7 @@
                     # Nothing can be dismissed without the list, and the
                     # failure has to reach the metric operators watch.
                     raise RuntimeError("Stampbot reviews could not be listed")
-                if keep_oldest:
+                if not keep_oldest:
                     # Duplicate cleanup only touches approvals proven to be of
                     # this head, and leaves the oldest of them in place.
                     review_ids = self._active_approvals_for_head(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x765960d62520>
mock_github_client = <MagicMock name='github_client' id='130126270742016'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_not_called()

tests/test_webhook_handler.py:118: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='130126271021472'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to not have been called. Called 1 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 777, 'Duplicate Stampbot approval')].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:17:01.313289Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:17:01.313757Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:17:01.313957Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:17:01.314550Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:17:01.314731Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "existing_review_ids": [777]}, "event": "PR #42 in octocat/hello-world already has active approval for current head, skipping", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:17:01.314885Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:17:01.313289Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:17:01.313757Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:17:01.313957Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:17:01.314550Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:17:01.314731Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:974 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'existing_review_ids': [777]}, 'event': 'PR #42 in octocat/hello-world already has active approval for current head, skipping', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:17:01.314885Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'dismiss_approval' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42, 777, 'Duplicate Stampbot approval')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.42s
operator: core/AddNot, occurrence: 71, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1090,7 +1090,7 @@
 
                 add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
 
-                if not review_ids:
+                if not not review_ids:
                     logger.debug(
                         "No Stampbot approvals to dismiss on PR #%d",
                         pr_number,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7ec2c318fdf0>
mock_github_client = <MagicMock name='github_client' id='139374983778320'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='139374983782016'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:23:18.144193Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:23:18.144602Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:23:18.144805Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:23:18.144193Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:23:18.144602Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:23:18.144805Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.41s
operator: core/AddNot, occurrence: 72, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1117,7 +1117,7 @@
                 duration = time.time() - start_time
                 pr_dismissal_duration_seconds.observe(duration)
 
-                status = "success" if success else "failure"
+                status = "success" if not success else "failure"
                 pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
 
                 add_span_attributes(span, {"dismissal.result": status})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.....................................................................F
=================================== FAILURES ===================================
________________ test_pr_duplicate_dismissal_failure_is_counted ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d20ee8b80c0>
mock_github_client = <MagicMock name='github_client' id='137580414278288'>

    @pytest.mark.asyncio
    async def test_pr_duplicate_dismissal_failure_is_counted(webhook_handler, mock_github_client):
        """Test a dismissal that GitHub rejects is recorded without failing the approval."""
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
            ],
        ]
        mock_github_client.dismiss_approval.return_value = False
        from stampbot.metrics import pr_dismissals_total
    
        failures = pr_dismissals_total.labels(trigger_type="duplicate", status="failure")
        before = failures._value.get()
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.dismiss_approval.assert_called_once()
>       assert failures._value.get() == before + 1
E       assert 5.0 == (5.0 + 1)
E        +  where 5.0 = get()
E        +    where get = <prometheus_client.values.MutexValue object at 0x7d20efdc8e30>.get
E        +      where <prometheus_client.values.MutexValue object at 0x7d20efdc8e30> = prometheus_client.metrics.Counter(stampbot_pr_dismissals)._value

tests/test_webhook_handler.py:908: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:53:42.012972Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:53:42.013417Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:53:42.013627Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:53:42.012972Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:53:42.013417Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:53:42.013627Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_duplicate_dismissal_failure_is_counted - assert 5.0 == (5.0 + 1)
 +  where 5.0 = get()
 +    where get = <prometheus_client.values.MutexValue object at 0x7d20efdc8e30>.get
 +      where <prometheus_client.values.MutexValue object at 0x7d20efdc8e30> = prometheus_client.metrics.Counter(stampbot_pr_dismissals)._value
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 429 passed, 3 deselected, 1 warning in 2.89s
operator: core/ReplaceTrueWithFalse, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -50,7 +50,7 @@
     NONE = "none"
 
 
-@dataclass(frozen=True)
+@dataclass(frozen=False)
 class PullRequestEvent:
     """The parts of a pull request webhook that policy decisions read."""
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceTrueWithFalse, occurrence: 1, definition_name: _refresh_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -379,7 +379,7 @@
             add_span_attributes(span, {"webhook.result": "no_action"})
             set_span_ok(span)
             return dict(NO_ACTION)
-        return await self._approve_for_label(span, event, label, skip_existing_check=True)
+        return await self._approve_for_label(span, event, label, skip_existing_check=False)
 
     async def _approve_for_label(
         self,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceTrueWithFalse, occurrence: 2, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -989,7 +989,7 @@
                         },
                     )
                     set_span_ok(span)
-                    return True
+                    return False
 
             start_time = time.time()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7436c09ce520>
mock_github_client = <MagicMock name='github_client' id='127778530049536'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
>       assert [r["status"] for r in results] == ["success", "success"]
E       AssertionError: assert ['success', 'error'] == ['success', 'success']
E         
E         At index 1 diff: 'error' != 'success'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E               'success',
E         -     'success',
E         +     'error',
E           ]

tests/test_webhook_handler.py:116: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:38:28.952507Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:38:28.952991Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:38:28.953207Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:38:28.953710Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:38:28.953882Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "existing_review_ids": [777]}, "event": "PR #42 in octocat/hello-world already has active approval for current head, skipping", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:38:28.954039Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:38:28.952507Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:38:28.952991Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:38:28.953207Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:38:28.953710Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:38:28.953882Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:974 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'existing_review_ids': [777]}, 'event': 'PR #42 in octocat/hello-world already has active approval for current head, skipping', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:38:28.954039Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: assert ['success', 'error'] == ['success', 'success']
  
  At index 1 diff: 'error' != 'success'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        'success',
  -     'success',
  +     'error',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.39s
operator: core/ReplaceTrueWithFalse, occurrence: 3, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1023,7 +1023,7 @@
                     "Duplicate Stampbot approval",
                     "duplicate",
                     head_sha=head_sha,
-                    keep_oldest=True,
+                    keep_oldest=False,
                 )
 
             add_span_attributes(span, {"approval.result": status})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73a69498a520>
mock_github_client = <MagicMock name='github_client' id='127159316479488'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_not_called()

tests/test_webhook_handler.py:118: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='127159316758944'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to not have been called. Called 1 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 777, 'Duplicate Stampbot approval')].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:08:08.211039Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:08:08.211523Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:08:08.211720Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:08:08.212334Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:08:08.212509Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "existing_review_ids": [777]}, "event": "PR #42 in octocat/hello-world already has active approval for current head, skipping", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:08:08.212687Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:08:08.211039Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:08:08.211523Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:08:08.211720Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:08:08.212334Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:08:08.212509Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:974 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'existing_review_ids': [777]}, 'event': 'PR #42 in octocat/hello-world already has active approval for current head, skipping', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:08:08.212687Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'dismiss_approval' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42, 777, 'Duplicate Stampbot approval')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceTrueWithFalse, occurrence: 4, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1083,7 +1083,7 @@
                     # Duplicate cleanup only touches approvals proven to be of
                     # this head, and leaves the oldest of them in place.
                     review_ids = self._active_approvals_for_head(
-                        reviews, head_sha, proven_only=True
+                        reviews, head_sha, proven_only=False
                     )[1:]
                 else:
                     review_ids = self._active_approvals_for_head(reviews, head_sha)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x70fb0e769d90>
mock_github_client = <MagicMock name='github_client' id='124223601237296'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
>       mock_github_client.dismiss_approval.assert_not_called()

tests/test_webhook_handler.py:300: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='124223601244016'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to not have been called. Called 1 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 2, 'Duplicate Stampbot approval')].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:30:32.679131Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:30:32.679605Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:30:32.679131Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:30:32.679605Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: Expected 'dismiss_approval' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42, 2, 'Duplicate Stampbot approval')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceTrueWithFalse, occurrence: 5, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1099,7 +1099,7 @@
                     pr_dismissal_duration_seconds.observe(time.time() - start_time)
                     add_span_attributes(span, {"dismissal.result": "no_reviews"})
                     set_span_ok(span)
-                    return True
+                    return False
 
                 # Dismiss each review
                 success = True
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.........................................................F
=================================== FAILURES ===================================
_______________________ test_pr_unlabeled_no_bot_reviews _______________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7af5938340c0>
mock_github_client = <MagicMock name='github_client' id='135195181008608'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_no_bot_reviews(webhook_handler, mock_github_client):
        """Test removing label when no bot reviews exist."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = []  # No reviews to dismiss
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:700: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:57:14.360057Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:57:14.360496Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:57:14.360602Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:57:14.360057Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:57:14.360496Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:57:14.360602Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_no_bot_reviews - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 417 passed, 3 deselected, 1 warning in 2.53s
operator: core/ReplaceTrueWithFalse, occurrence: 6, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1102,7 +1102,7 @@
                     return True
 
                 # Dismiss each review
-                success = True
+                success = False
                 for review_id in review_ids:
                     result = await run_in_threadpool(
                         github_client.dismiss_approval,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......................................................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77e38bd2b230>
mock_github_client = <MagicMock name='github_client' id='131819277090304'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
        """Test removing autoapprove label dismisses approvals."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"},
            {"id": 222, "state": "APPROVED", "commit_id": "abc123def456"},
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'error' == 'success'
E         
E         - success
E         + error

tests/test_webhook_handler.py:674: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:52:05.680597Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:52:05.681010Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:52:05.681105Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:52:05.680597Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:52:05.681010Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:52:05.681105Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 'error' == 'success'
  
  - success
  + error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 415 passed, 3 deselected, 1 warning in 2.53s
operator: core/ReplaceFalseWithTrue, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -112,7 +112,7 @@
             True if signature is valid
         """
         if not signature:
-            return False
+            return True
 
         expected_signature = (
             "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..........F
=================================== FAILURES ===================================
________________________ test_webhook_missing_signature ________________________

test_client = <starlette.testclient.TestClient object at 0x721b809bf570>

    def test_webhook_missing_signature(test_client: TestClient):
        """Test webhook rejects requests without signature."""
        response = test_client.post(
            "/webhook",
            json={"zen": "test"},
            headers={"X-GitHub-Event": "ping"},
        )
>       assert response.status_code == 401
E       assert 200 == 401
E        +  where 200 = <Response [200 OK]>.status_code

tests/test_main.py:279: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:53:28.333593Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:53:28.334149Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:53:28.333593Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_missing_signature - assert 200 == 401
 +  where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 226 passed, 3 deselected, 1 warning in 2.12s
operator: core/ReplaceFalseWithTrue, occurrence: 1, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -121,7 +121,7 @@
         try:
             return hmac.compare_digest(expected_signature, signature)
         except TypeError:
-            return False
+            return True
 
     async def handle_event(
         self,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...........................F
=================================== FAILURES ===================================
_______________________ test_verify_signature_non_ascii ________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73b96be0a0b0>

    def test_verify_signature_non_ascii(webhook_handler):
        """Test non-ASCII signature content is rejected."""
        payload = b'{"test": "data"}'
        signature = "sha256=\u2603"
    
>       assert webhook_handler.verify_signature(payload, signature) is False
E       assert True is False
E        +  where True = verify_signature(b'{"test": "data"}', 'sha256=☃')
E        +    where verify_signature = <stampbot.webhook_handler.WebhookHandler object at 0x73b96be0a0b0>.verify_signature

tests/test_webhook_handler.py:1437: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_verify_signature_non_ascii - assert True is False
 +  where True = verify_signature(b'{"test": "data"}', 'sha256=☃')
 +    where verify_signature = <stampbot.webhook_handler.WebhookHandler object at 0x73b96be0a0b0>.verify_signature
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 459 passed, 3 deselected, 1 warning in 3.29s
operator: core/ReplaceFalseWithTrue, occurrence: 2, definition_name: _approve_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -386,7 +386,7 @@
         span: Span | None,
         event: PullRequestEvent,
         label: str,
-        skip_existing_check: bool = False,
+        skip_existing_check: bool = True,
     ) -> dict[str, Any]:
         """Approve a pull request that carries an approval label, if it is eligible.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x76463eb92520>
mock_github_client = <MagicMock name='github_client' id='130044098870784'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='130044098861376'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:52:09.253519Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:52:09.253999Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:52:09.254213Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:52:09.254625Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:52:09.254798Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:52:09.253519Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:52:09.253999Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:52:09.254213Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:52:09.254625Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:52:09.254798Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.42s
operator: core/ReplaceFalseWithTrue, occurrence: 3, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -531,7 +531,7 @@
                 event.repo_full_name,
                 label,
             )
-            if label_exists is False:
+            if label_exists is True:
                 logger.warning(
                     "Approval label %s not found in %s",
                     label,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceFalseWithTrue, occurrence: 4, definition_name: _approval_needs_refresh
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -554,7 +554,7 @@
             True when a prior approval exists but no active approval applies to the current head.
         """
         if not reviews:
-            return False
+            return True
 
         return not self._has_current_active_approval(reviews, head_sha)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...................................F
=================================== FAILURES ===================================
_________ test_pr_labeled_unrelated_label_with_approval_label_ignored __________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x720c9f513f50>
mock_github_client = <MagicMock name='github_client' id='125398557967088'>

    @pytest.mark.asyncio
    async def test_pr_labeled_unrelated_label_with_approval_label_ignored(
        webhook_handler, mock_github_client
    ):
        """Test unrelated label changes do not re-approve just because an approval label exists."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["label"] = {"name": "bug"}
        payload["pull_request"]["labels"].append({"name": "bug"})
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "ignored"
E       AssertionError: assert 'success' == 'ignored'
E         
E         - ignored
E         + success

tests/test_webhook_handler.py:496: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:31:15.196785Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:31:15.197236Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:31:15.197538Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:31:15.196785Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:31:15.197236Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:31:15.197538Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_unrelated_label_with_approval_label_ignored - AssertionError: assert 'success' == 'ignored'
  
  - ignored
  + success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 395 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceFalseWithTrue, occurrence: 5, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -563,7 +563,7 @@
         reviews: list[BotReview],
         head_sha: str | None,
         *,
-        proven_only: bool = False,
+        proven_only: bool = True,
     ) -> list[int]:
         """Return the IDs of Stampbot approvals that cover the current head, oldest first.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.............F
=================================== FAILURES ===================================
___________ test_active_approvals_for_head[reviews4-None-expected4] ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73f7d9c7e690>
reviews = [{'id': 1, 'state': 'APPROVED', 'commit_id': None}, {'id': 2, 'state': 'APPROVED', 'commit_id': 'head'}]
head_sha = None, expected = [1, 2]

    @pytest.mark.parametrize(
        ("reviews", "head_sha", "expected"),
        [
            ([], "head", []),
            ([{"id": 1, "state": "APPROVED", "commit_id": "head"}], "head", [1]),
            (
                [
                    {"id": 3, "state": "APPROVED", "commit_id": "head"},
                    {"id": 1, "state": "APPROVED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [1, 2, 3],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": "old"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": None},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                None,
                [1, 2],
            ),
            (
                [
                    {"id": 1, "state": "DISMISSED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
        ],
    )
    def test_active_approvals_for_head(webhook_handler, reviews, head_sha, expected):
        """Test which approvals count as covering the head, oldest first."""
>       assert webhook_handler._active_approvals_for_head(reviews, head_sha) == expected
E       assert [] == [1, 2]
E         
E         Right contains 2 more items, first extra item: 1
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + []
E         - [
E         -     1,
E         -     2,
E         - ]

tests/test_webhook_handler.py:256: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_active_approvals_for_head[reviews4-None-expected4] - assert [] == [1, 2]
  
  Right contains 2 more items, first extra item: 1
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     1,
  -     2,
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 373 passed, 3 deselected, 1 warning in 2.51s
operator: core/ReplaceFalseWithTrue, occurrence: 6, definition_name: _approve_pr
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -919,7 +919,7 @@
         pr_number: int,
         comment: str,
         trigger_type: str,
-        skip_existing_check: bool = False,
+        skip_existing_check: bool = True,
         head_sha: str | None = None,
     ) -> bool:
         """Approve a PR and track metrics.
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................................................................F
=================================== FAILURES ===================================
__________________ test_issue_comment_approve_skips_duplicate __________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x70a827757310>
mock_github_client = <MagicMock name='github_client' id='123867541211184'>

    @pytest.mark.asyncio
    async def test_issue_comment_approve_skips_duplicate(webhook_handler, mock_github_client):
        """Test chatops approve skips duplicate when PR already approved."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 12345, "state": "APPROVED", "commit_id": "current-head"}
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
        assert result["status"] == "success"
        mock_github_client.get_pr_head_sha.assert_called_once()
        mock_github_client.find_bot_approval_reviews.assert_called_once()
>       mock_github_client.approve_pr.assert_not_called()

tests/test_webhook_handler.py:848: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='123867541210848'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to not have been called. Called 1 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Approved by @maintainer via chatops')].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:03:27.827281Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:03:27.827696Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:03:27.827281Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:03:27.827696Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_issue_comment_approve_skips_duplicate - AssertionError: Expected 'approve_pr' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Approved by @maintainer via chatops')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 426 passed, 3 deselected, 1 warning in 2.84s
operator: core/ReplaceFalseWithTrue, occurrence: 7, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1040,7 +1040,7 @@
         trigger_type: str,
         *,
         head_sha: str | None = None,
-        keep_oldest: bool = False,
+        keep_oldest: bool = True,
     ) -> bool:
         """Dismiss active Stampbot approvals on a PR.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.......................................................F
=================================== FAILURES ===================================
________________________ test_pr_unlabeled_autoapprove _________________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7191edd66eb0>
mock_github_client = <MagicMock name='github_client' id='124871595146752'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_autoapprove(webhook_handler, mock_github_client):
        """Test removing autoapprove label dismisses approvals."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = [
            {"id": 111, "state": "APPROVED", "commit_id": "abc123def456"},
            {"id": 222, "state": "APPROVED", "commit_id": "abc123def456"},
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        assert "dismissed" in result["message"].lower()
        mock_github_client.find_bot_approval_reviews.assert_called_once()
>       assert mock_github_client.dismiss_approval.call_count == 2
E       AssertionError: assert 0 == 2
E        +  where 0 = <MagicMock name='github_client.dismiss_approval' id='124871595950496'>.call_count
E        +    where <MagicMock name='github_client.dismiss_approval' id='124871595950496'> = <MagicMock name='github_client' id='124871595146752'>.dismiss_approval

tests/test_webhook_handler.py:677: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:20:59.907993Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:20:59.908425Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:20:59.908521Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:20:59.907993Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:20:59.908425Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:20:59.908521Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_autoapprove - AssertionError: assert 0 == 2
 +  where 0 = <MagicMock name='github_client.dismiss_approval' id='124871595950496'>.call_count
 +    where <MagicMock name='github_client.dismiss_approval' id='124871595950496'> = <MagicMock name='github_client' id='124871595146752'>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 415 passed, 3 deselected, 1 warning in 2.51s
operator: core/ReplaceFalseWithTrue, occurrence: 8, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1142,7 +1142,7 @@
                 errors_total.labels(error_type="dismiss_failed").inc()
 
                 set_span_error(span, e)
-                return False
+                return True
 
 
 # Global handler instance
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d044797aa80>
mock_github_client = <MagicMock name='github_client' id='137457334027664'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "error"
E       AssertionError: assert 'success' == 'error'
E         
E         - error
E         + success

tests/test_webhook_handler.py:210: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:08:58.527464Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:08:58.527870Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "Approval label autoapprove removed from PR #42", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:08:58.527965Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "Stampbot reviews could not be listed"}, "event": "Error dismissing approvals: Stampbot reviews could not be listed", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T03:08:58.528157Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:08:58.527464Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:08:58.527870Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:486 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'Approval label autoapprove removed from PR #42', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:08:58.527965Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': 'Stampbot reviews could not be listed'}, 'event': 'Error dismissing approvals: Stampbot reviews could not be listed', 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T03:08:58.528157Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - AssertionError: assert 'success' == 'error'
  
  - error
  + success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceAndWithOr, occurrence: 0, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -275,7 +275,7 @@
             )
 
             decision, label = self._decide(event)
-            if decision is Decision.APPROVE and label:
+            if decision is Decision.APPROVE or label:
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x71a0d45d6030>
mock_github_client = <MagicMock name='github_client' id='124935598966160'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "error"
E       AssertionError: assert 'success' == 'error'
E         
E         - error
E         + success

tests/test_webhook_handler.py:210: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:28:31.537621Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:28:31.538039Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:28:31.538262Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42}, "event": "Could not read reviews for PR #42 in octocat/hello-world; approving without a check", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:28:31.538435Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "Stampbot reviews could not be listed"}, "event": "Error dismissing approvals: Stampbot reviews could not be listed", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T04:28:31.538694Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:28:31.537621Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:28:31.538039Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:28:31.538262Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:965 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'Could not read reviews for PR #42 in octocat/hello-world; approving without a check', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:28:31.538435Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': 'Stampbot reviews could not be listed'}, 'event': 'Error dismissing approvals: Stampbot reviews could not be listed', 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T04:28:31.538694Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - AssertionError: assert 'success' == 'error'
  
  - error
  + success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceAndWithOr, occurrence: 1, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -277,7 +277,7 @@
             decision, label = self._decide(event)
             if decision is Decision.APPROVE and label:
                 result = await self._approve_for_label(span, event, label)
-            elif decision is Decision.REFRESH and label:
+            elif decision is Decision.REFRESH or label:
                 result = await self._refresh_for_label(span, event, label)
             elif decision is Decision.DISMISS and label:
                 result = await self._dismiss_for_label(span, event, label)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........F
=================================== FAILURES ===================================
_________________ test_pr_unlabeled_unknown_review_state_fails _________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x703819992030>
mock_github_client = <MagicMock name='github_client' id='123386277594512'>

    @pytest.mark.asyncio
    async def test_pr_unlabeled_unknown_review_state_fails(webhook_handler, mock_github_client):
        """Test a dismissal cannot succeed when the review list is unavailable."""
        payload = load_fixture("pr_unlabeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.return_value = None
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "error"
E       AssertionError: assert 'success' == 'error'
E         
E         - error
E         + success

tests/test_webhook_handler.py:210: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "unlabeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:06:39.397226Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:06:39.397637Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42}, "event": "Could not read reviews for PR #42 in octocat/hello-world; approving without a refresh check", "logger": "stampbot.webhook_handler", "level": "warning", "timestamp": "2026-11-04T04:06:39.397836Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "warning"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:06:39.397986Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "error": "Stampbot reviews could not be listed"}, "event": "Error dismissing approvals: Stampbot reviews could not be listed", "logger": "stampbot.webhook_handler", "level": "error", "timestamp": "2026-11-04T04:06:39.398279Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "error"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'unlabeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:06:39.397226Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:06:39.397637Z'}
WARNING  stampbot.webhook_handler:webhook_handler.py:372 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42}, 'event': 'Could not read reviews for PR #42 in octocat/hello-world; approving without a refresh check', 'logger': 'stampbot.webhook_handler', 'level': 'warning', 'timestamp': '2026-11-04T04:06:39.397836Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:06:39.397986Z'}
ERROR    stampbot.webhook_handler:webhook_handler.py:1133 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'error': 'Stampbot reviews could not be listed'}, 'event': 'Error dismissing approvals: Stampbot reviews could not be listed', 'logger': 'stampbot.webhook_handler', 'level': 'error', 'timestamp': '2026-11-04T04:06:39.398279Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_unlabeled_unknown_review_state_fails - AssertionError: assert 'success' == 'error'
  
  - error
  + success
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 368 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceAndWithOr, occurrence: 2, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -279,7 +279,7 @@
                 result = await self._approve_for_label(span, event, label)
             elif decision is Decision.REFRESH and label:
                 result = await self._refresh_for_label(span, event, label)
-            elif decision is Decision.DISMISS and label:
+            elif decision is Decision.DISMISS or label:
                 result = await self._dismiss_for_label(span, event, label)
             else:
                 add_span_attributes(span, {"webhook.result": "no_action"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceAndWithOr, occurrence: 3, definition_name: _decide
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -327,7 +327,7 @@
                 return Decision.APPROVE, label
             return Decision.REFRESH, label
 
-        if event.action == "synchronize" and config.reapprove:
+        if event.action == "synchronize" or config.reapprove:
             return Decision.REFRESH, label
 
         return Decision.NONE, None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.....................................F
=================================== FAILURES ===================================
____________________ test_pr_synchronize_reapprove_disabled ____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77fba2204c90>
mock_github_client = <MagicMock name='github_client' id='131922662170240'>

    @pytest.mark.asyncio
    async def test_pr_synchronize_reapprove_disabled(webhook_handler, mock_github_client):
        """Test new commits do not re-approve by default."""
        payload = load_fixture("pr_labeled_autoapprove")
        payload["action"] = "synchronize"
        payload.pop("label", None)
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "ignored"
        assert "no action" in result["message"].lower()
>       mock_github_client.find_bot_approval_reviews.assert_not_called()

tests/test_webhook_handler.py:531: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.find_bot_approval_reviews' id='131922662164864'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'find_bot_approval_reviews' to not have been called. Called 1 times.
E           Calls: [call(12345, 'octocat/hello-world', 42)].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "synchronize"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:39:56.807266Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:39:56.807667Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'synchronize'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:39:56.807266Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:39:56.807667Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_synchronize_reapprove_disabled - AssertionError: Expected 'find_bot_approval_reviews' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42)].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 397 passed, 3 deselected, 1 warning in 2.48s
operator: core/ReplaceAndWithOr, occurrence: 4, definition_name: _approve_for_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -404,7 +404,7 @@
         pr_author = event.pr.get("user", {}).get("login", "")
 
         author_team_slugs: list[str] | None = None
-        if config.needs_team_check(pr_author) and event.owner_login:
+        if config.needs_team_check(pr_author) or event.owner_login:
             author_team_slugs = await run_in_threadpool(
                 github_client.get_user_team_slugs,
                 event.installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...........................................F
=================================== FAILURES ===================================
_________________ test_pr_with_allowed_users_skips_team_check __________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x707f537cfaf0>
mock_github_client = <MagicMock name='github_client' id='123692038038464'>

    @pytest.mark.asyncio
    async def test_pr_with_allowed_users_skips_team_check(webhook_handler, mock_github_client):
        """Test PR with user in allowed_users skips team membership check."""
        payload = load_fixture("pr_labeled_autoapprove")
        # The fixture has user "contributor"
        mock_github_client.get_repo_file.return_value = """
    allowed_users = ["contributor"]
    allowed_teams = ["acme/release-team"]
    """
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        # Should NOT call get_user_team_slugs since user is in allowed_users
>       mock_github_client.get_user_team_slugs.assert_not_called()

tests/test_webhook_handler.py:1751: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.get_user_team_slugs' id='123692038036448'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_user_team_slugs' to not have been called. Called 1 times.
E           Calls: [call(12345, 'octocat', 'contributor', ['acme/release-team'])].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:47:53.797390Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:47:53.798048Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:47:53.797390Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:47:53.798048Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_with_allowed_users_skips_team_check - AssertionError: Expected 'get_user_team_slugs' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat', 'contributor', ['acme/release-team'])].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 475 passed, 3 deselected, 1 warning in 3.43s
operator: core/ReplaceAndWithOr, occurrence: 5, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -586,7 +586,7 @@
         def covers_head(review: BotReview) -> bool:
             commit = review.get("commit_id")
             if proven_only:
-                return bool(head_sha) and commit == head_sha
+                return bool(head_sha) or commit == head_sha
             return not head_sha or not commit or commit == head_sha
 
         return sorted(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d709b5afdf0>
mock_github_client = <MagicMock name='github_client' id='137922618118160'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='137922618121856'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 100, 'Duplicate Stampbot approval'),
E            call(12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:44:21.109526Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:44:21.109938Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:44:21.110125Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:44:21.109526Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:44:21.109938Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:44:21.110125Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 100, 'Duplicate Stampbot approval'),
 call(12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceAndWithOr, occurrence: 6, definition_name: _active_approvals_for_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -592,7 +592,7 @@
         return sorted(
             review["id"]
             for review in reviews
-            if review["state"] == "APPROVED" and covers_head(review)
+            if review["state"] == "APPROVED" or covers_head(review)
         )
 
     def _has_current_active_approval(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c799159bdf0>
mock_github_client = <MagicMock name='github_client' id='136861593374736'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='136861593378432'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 3 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 60, 'Duplicate Stampbot approval'),
E            call(12345, 'octocat/hello-world', 42, 100, 'Duplicate Stampbot approval'),
E            call(12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:04:49.529884Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:04:49.530296Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:04:49.530480Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:04:49.529884Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:04:49.530296Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:04:49.530480Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 3 times.
Calls: [call(12345, 'octocat/hello-world', 42, 60, 'Duplicate Stampbot approval'),
 call(12345, 'octocat/hello-world', 42, 100, 'Duplicate Stampbot approval'),
 call(12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.43s
operator: core/ReplaceAndWithOr, occurrence: 7, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1112,7 +1112,7 @@
                         review_id,
                         message,
                     )
-                    success = success and result
+                    success = success or result
 
                 duration = time.time() - start_time
                 pr_dismissal_duration_seconds.observe(duration)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.....................................................................F
=================================== FAILURES ===================================
________________ test_pr_duplicate_dismissal_failure_is_counted ________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x73c87fad73f0>
mock_github_client = <MagicMock name='github_client' id='127304993659536'>

    @pytest.mark.asyncio
    async def test_pr_duplicate_dismissal_failure_is_counted(webhook_handler, mock_github_client):
        """Test a dismissal that GitHub rejects is recorded without failing the approval."""
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
            ],
        ]
        mock_github_client.dismiss_approval.return_value = False
        from stampbot.metrics import pr_dismissals_total
    
        failures = pr_dismissals_total.labels(trigger_type="duplicate", status="failure")
        before = failures._value.get()
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.dismiss_approval.assert_called_once()
>       assert failures._value.get() == before + 1
E       assert 4.0 == (4.0 + 1)
E        +  where 4.0 = get()
E        +    where get = <prometheus_client.values.MutexValue object at 0x73c880f3dc70>.get
E        +      where <prometheus_client.values.MutexValue object at 0x73c880f3dc70> = prometheus_client.metrics.Counter(stampbot_pr_dismissals)._value

tests/test_webhook_handler.py:908: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:21:13.308530Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:21:13.308978Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:21:13.309185Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:21:13.308530Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:21:13.308978Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:21:13.309185Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_duplicate_dismissal_failure_is_counted - assert 4.0 == (4.0 + 1)
 +  where 4.0 = get()
 +    where get = <prometheus_client.values.MutexValue object at 0x73c880f3dc70>.get
 +      where <prometheus_client.values.MutexValue object at 0x73c880f3dc70> = prometheus_client.metrics.Counter(stampbot_pr_dismissals)._value
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 429 passed, 3 deselected, 1 warning in 2.86s
operator: core/ReplaceOrWithAnd, occurrence: 0, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -194,7 +194,7 @@
         pr_number = pr.get("number")
         repo = payload.get("repository", {})
         repo_full_name = repo.get("full_name")
-        repo_default_branch = repo.get("default_branch") or "main"
+        repo_default_branch = repo.get("default_branch") and "main"
         repo_owner = repo.get("owner", {})
         owner_login = repo_owner.get("login")
         owner_type = repo_owner.get("type")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.............................F
=================================== FAILURES ===================================
______________ test_repo_config_cache_key_includes_default_branch ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7872f7abfee0>
mock_github_client = <MagicMock name='github_client' id='132435199975712'>

    @pytest.mark.asyncio
    async def test_repo_config_cache_key_includes_default_branch(webhook_handler, mock_github_client):
        """Test policy read from another default branch is not served from the cache."""
        payload = load_fixture("pr_labeled_autoapprove")
    
        await webhook_handler.handle_event("pull_request", payload)
        payload["repository"]["default_branch"] = "develop"
        await webhook_handler.handle_event("pull_request", payload)
    
>       assert mock_github_client.get_repo_file.call_count == 2
E       AssertionError: assert 1 == 2
E        +  where 1 = <MagicMock name='github_client.get_repo_file' id='132435199974368'>.call_count
E        +    where <MagicMock name='github_client.get_repo_file' id='132435199974368'> = <MagicMock name='github_client' id='132435199975712'>.get_repo_file

tests/test_webhook_handler.py:414: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:11:34.870076Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:11:34.870510Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:11:34.870700Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:11:34.871057Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:11:34.871234Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:11:34.870076Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:11:34.870510Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:11:34.870700Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:11:34.871057Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:11:34.871234Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_repo_config_cache_key_includes_default_branch - AssertionError: assert 1 == 2
 +  where 1 = <MagicMock name='github_client.get_repo_file' id='132435199974368'>.call_count
 +    where <MagicMock name='github_client.get_repo_file' id='132435199974368'> = <MagicMock name='github_client' id='132435199975712'>.get_repo_file
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 389 passed, 3 deselected, 1 warning in 2.46s
operator: core/ReplaceOrWithAnd, occurrence: 1, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -203,7 +203,7 @@
         with create_span(
             "webhook.handle_pull_request",
             {
-                "github.repo": repo_full_name or "unknown",
+                "github.repo": repo_full_name and "unknown",
                 "github.pr_number": pr_number or 0,
                 "github.action": action or "unknown",
             },
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceOrWithAnd, occurrence: 2, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -204,7 +204,7 @@
             "webhook.handle_pull_request",
             {
                 "github.repo": repo_full_name or "unknown",
-                "github.pr_number": pr_number or 0,
+                "github.pr_number": pr_number and 0,
                 "github.action": action or "unknown",
             },
         ) as span:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceOrWithAnd, occurrence: 3, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -205,7 +205,7 @@
             {
                 "github.repo": repo_full_name or "unknown",
                 "github.pr_number": pr_number or 0,
-                "github.action": action or "unknown",
+                "github.action": action and "unknown",
             },
         ) as span:
             if not all([pr_number, repo_full_name, installation_id]):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceOrWithAnd, occurrence: 4, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -587,7 +587,7 @@
             commit = review.get("commit_id")
             if proven_only:
                 return bool(head_sha) and commit == head_sha
-            return not head_sha or not commit or commit == head_sha
+            return not head_sha and not commit or commit == head_sha
 
         return sorted(
             review["id"]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.............F
=================================== FAILURES ===================================
___________ test_active_approvals_for_head[reviews4-None-expected4] ____________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74903a346690>
reviews = [{'id': 1, 'state': 'APPROVED', 'commit_id': None}, {'id': 2, 'state': 'APPROVED', 'commit_id': 'head'}]
head_sha = None, expected = [1, 2]

    @pytest.mark.parametrize(
        ("reviews", "head_sha", "expected"),
        [
            ([], "head", []),
            ([{"id": 1, "state": "APPROVED", "commit_id": "head"}], "head", [1]),
            (
                [
                    {"id": 3, "state": "APPROVED", "commit_id": "head"},
                    {"id": 1, "state": "APPROVED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [1, 2, 3],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": "old"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
            (
                [
                    {"id": 1, "state": "APPROVED", "commit_id": None},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                None,
                [1, 2],
            ),
            (
                [
                    {"id": 1, "state": "DISMISSED", "commit_id": "head"},
                    {"id": 2, "state": "APPROVED", "commit_id": "head"},
                ],
                "head",
                [2],
            ),
        ],
    )
    def test_active_approvals_for_head(webhook_handler, reviews, head_sha, expected):
        """Test which approvals count as covering the head, oldest first."""
>       assert webhook_handler._active_approvals_for_head(reviews, head_sha) == expected
E       assert [1] == [1, 2]
E         
E         Right contains one more item: 2
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E               1,
E         -     2,
E           ]

tests/test_webhook_handler.py:256: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_active_approvals_for_head[reviews4-None-expected4] - assert [1] == [1, 2]
  
  Right contains one more item: 2
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        1,
  -     2,
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 373 passed, 3 deselected, 1 warning in 2.44s
operator: core/ReplaceOrWithAnd, occurrence: 5, definition_name: covers_head
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -587,7 +587,7 @@
             commit = review.get("commit_id")
             if proven_only:
                 return bool(head_sha) and commit == head_sha
-            return not head_sha or not commit or commit == head_sha
+            return not head_sha or not commit and commit == head_sha
 
         return sorted(
             review["id"]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75067bc7e520>
mock_github_client = <MagicMock name='github_client' id='128670803867504'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='128670803859776'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:11:33.388512Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:11:33.389007Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:11:33.389228Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:11:33.389746Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:11:33.389936Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:11:33.388512Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:11:33.389007Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:11:33.389228Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:11:33.389746Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:11:33.389936Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceOrWithAnd, occurrence: 6, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -645,7 +645,7 @@
 
         repo = payload.get("repository", {})
         repo_full_name = repo.get("full_name")
-        repo_default_branch = repo.get("default_branch") or "main"
+        repo_default_branch = repo.get("default_branch") and "main"
         repo_owner = repo.get("owner", {})
         owner_login = repo_owner.get("login")
         owner_type = repo_owner.get("type")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceOrWithAnd, occurrence: 7, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -655,7 +655,7 @@
         with create_span(
             "webhook.handle_chatops",
             {
-                "github.repo": repo_full_name or "unknown",
+                "github.repo": repo_full_name and "unknown",
                 "github.pr_number": pr_number,
                 "chatops.commenter": commenter,
             },
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceContinueWithBreak, occurrence: 0, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -524,7 +524,7 @@
         present = event.labels
         for label in event.repo_config.approval_labels:
             if label in present:
-                continue
+                break
             label_exists = await run_in_threadpool(
                 github_client.repo_has_label,
                 event.installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x705cabc4bb60>
mock_github_client = <MagicMock name='github_client' id='123543410899808'>

    @pytest.mark.asyncio
    async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
        """Test missing approval label triggers label check after the review decision."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        mock_github_client.repo_has_label.return_value = False
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        # The label on the pull request exists by definition; only "stamp" is checked.
>       mock_github_client.repo_has_label.assert_called_once_with(12345, "octocat/hello-world", "stamp")

tests/test_webhook_handler.py:352: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.repo_has_label' id='123543410885360'>
args = (12345, 'octocat/hello-world', 'stamp'), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:44:16.636053Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:44:16.636493Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:44:16.636694Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:44:16.636053Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:44:16.636493Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:44:16.636694Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 382 passed, 3 deselected, 1 warning in 2.47s
operator: core/ExceptionReplacer, occurrence: 0, definition_name: verify_signature
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -120,7 +120,7 @@
 
         try:
             return hmac.compare_digest(expected_signature, signature)
-        except TypeError:
+        except CosmicRayTestingException:
             return False
 
     async def handle_event(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
...........................F
=================================== FAILURES ===================================
_______________________ test_verify_signature_non_ascii ________________________

self = <stampbot.webhook_handler.WebhookHandler object at 0x75fac30e64a0>
payload = b'{"test": "data"}', signature = 'sha256=☃'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
    
        try:
>           return hmac.compare_digest(expected_signature, signature)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: comparing strings with non-ASCII characters is not supported

stampbot/webhook_handler.py:122: TypeError

During handling of the above exception, another exception occurred:

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x75fac30e64a0>

    def test_verify_signature_non_ascii(webhook_handler):
        """Test non-ASCII signature content is rejected."""
        payload = b'{"test": "data"}'
        signature = "sha256=\u2603"
    
>       assert webhook_handler.verify_signature(payload, signature) is False
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:1437: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x75fac30e64a0>
payload = b'{"test": "data"}', signature = 'sha256=☃'

    def verify_signature(self, payload: bytes, signature: str) -> bool:
        """Verify GitHub webhook signature.
    
        Args:
            payload: Request payload
            signature: X-Hub-Signature-256 header value
    
        Returns:
            True if signature is valid
        """
        if not signature:
            return False
    
        expected_signature = (
            "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
        )
    
        try:
            return hmac.compare_digest(expected_signature, signature)
>       except CosmicRayTestingException:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/webhook_handler.py:123: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_verify_signature_non_ascii - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 459 passed, 3 deselected, 1 warning in 3.35s
operator: core/ExceptionReplacer, occurrence: 1, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1125,7 +1125,7 @@
 
                 return success
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 pr_dismissal_duration_seconds.observe(duration)
                 pr_dismissals_total.labels(trigger_type=trigger_type, status="failure").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
....F
=================================== FAILURES ===================================
___ test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] ____

self = <stampbot.webhook_handler.WebhookHandler object at 0x7d90452abf00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
>               reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )

stampbot/webhook_handler.py:1072: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:34: in run_in_threadpool
    return await anyio.to_thread.run_sync(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:65: in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2779: in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:1124: in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.find_bot_approval_reviews' id='138058611111792'>
args = (12345, 'octocat/hello-world', 42), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   RuntimeError: GitHub unavailable

/usr/lib/python3.14/unittest/mock.py:1245: RuntimeError

During handling of the above exception, another exception occurred:

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7d90452abf00>
mock_github_client = <MagicMock name='github_client' id='138058611110448'>
failure = RuntimeError('GitHub unavailable')

    @pytest.mark.asyncio
    @pytest.mark.parametrize("failure", [RuntimeError("GitHub unavailable"), None])
    async def test_pr_duplicate_check_failure_keeps_approval_and_is_counted(
        webhook_handler, mock_github_client, failure
    ):
        """Test a failed duplicate scan keeps the approval and reaches the failure counter.
    
        The client reports a listing failure as None; an unexpected exception is
        handled the same way.
        """
        from stampbot.metrics import pr_dismissals_total
    
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [[], failure]
        failures = pr_dismissals_total.labels(trigger_type="duplicate", status="failure")
        before = failures._value.get()
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:166: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:449: in _approve_for_label
    success = await self._approve_pr(
stampbot/webhook_handler.py:1019: in _approve_pr
    await self._dismiss_approvals(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7d90452abf00>
installation_id = 12345, repo_full_name = 'octocat/hello-world', pr_number = 42
message = 'Duplicate Stampbot approval', trigger_type = 'duplicate'
head_sha = 'abc123def456', keep_oldest = True

    async def _dismiss_approvals(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
        trigger_type: str,
        *,
        head_sha: str | None = None,
        keep_oldest: bool = False,
    ) -> bool:
        """Dismiss active Stampbot approvals on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name
            pr_number: PR number
            message: Dismissal message
            trigger_type: What triggered the dismissal (label_removed, chatops, duplicate)
            head_sha: Only approvals covering this head are dismissed. None means every
                active approval.
            keep_oldest: Leave the oldest matching approval in place, which turns the
                call into duplicate cleanup.
    
        Returns:
            True if every selected approval was dismissed, or none needed dismissal.
        """
        with create_span(
            "webhook.dismiss_approvals",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "dismissal.trigger_type": trigger_type,
            },
        ) as span:
            start_time = time.time()
    
            try:
                reviews = await run_in_threadpool(
                    github_client.find_bot_approval_reviews,
                    installation_id,
                    repo_full_name,
                    pr_number,
                )
                if reviews is None:
                    # Nothing can be dismissed without the list, and the
                    # failure has to reach the metric operators watch.
                    raise RuntimeError("Stampbot reviews could not be listed")
                if keep_oldest:
                    # Duplicate cleanup only touches approvals proven to be of
                    # this head, and leaves the oldest of them in place.
                    review_ids = self._active_approvals_for_head(
                        reviews, head_sha, proven_only=True
                    )[1:]
                else:
                    review_ids = self._active_approvals_for_head(reviews, head_sha)
    
                add_span_attributes(span, {"dismissal.reviews_found": len(review_ids)})
    
                if not review_ids:
                    logger.debug(
                        "No Stampbot approvals to dismiss on PR #%d",
                        pr_number,
                        extra={"repo": repo_full_name, "pr_number": pr_number},
                    )
                    pr_dismissal_duration_seconds.observe(time.time() - start_time)
                    add_span_attributes(span, {"dismissal.result": "no_reviews"})
                    set_span_ok(span)
                    return True
    
                # Dismiss each review
                success = True
                for review_id in review_ids:
                    result = await run_in_threadpool(
                        github_client.dismiss_approval,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        review_id,
                        message,
                    )
                    success = success and result
    
                duration = time.time() - start_time
                pr_dismissal_duration_seconds.observe(duration)
    
                status = "success" if success else "failure"
                pr_dismissals_total.labels(trigger_type=trigger_type, status=status).inc()
    
                add_span_attributes(span, {"dismissal.result": status})
                set_span_ok(span)
    
                return success
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/webhook_handler.py:1128: NameError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:08:06.620324Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:08:06.620732Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:08:06.620924Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:08:06.620324Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:08:06.620732Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:08:06.620924Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_duplicate_check_failure_keeps_approval_and_is_counted[failure0] - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 364 passed, 3 deselected, 1 warning in 2.46s
operator: core/NumberReplacer, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -32,7 +32,7 @@
 logger = get_logger(__name__)
 
 # Security: Maximum length for user-controlled strings to prevent DoS
-MAX_COMMENT_LENGTH = 65536  # 64KB - generous but prevents abuse
+MAX_COMMENT_LENGTH = 65537  # 64KB - generous but prevents abuse
 
 # Pull request actions that can change Stampbot's review state. Every other
 # action returns before any GitHub request is made.
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/NumberReplacer, occurrence: 1, definition_name: None
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -32,7 +32,7 @@
 logger = get_logger(__name__)
 
 # Security: Maximum length for user-controlled strings to prevent DoS
-MAX_COMMENT_LENGTH = 65536  # 64KB - generous but prevents abuse
+MAX_COMMENT_LENGTH = 65535  # 64KB - generous but prevents abuse
 
 # Pull request actions that can change Stampbot's review state. Every other
 # action returns before any GitHub request is made.
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/NumberReplacer, occurrence: 2, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -204,7 +204,7 @@
             "webhook.handle_pull_request",
             {
                 "github.repo": repo_full_name or "unknown",
-                "github.pr_number": pr_number or 0,
+                "github.pr_number": pr_number or 1,
                 "github.action": action or "unknown",
             },
         ) as span:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 3, definition_name: _handle_pull_request
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -204,7 +204,7 @@
             "webhook.handle_pull_request",
             {
                 "github.repo": repo_full_name or "unknown",
-                "github.pr_number": pr_number or 0,
+                "github.pr_number": pr_number or -1,
                 "github.action": action or "unknown",
             },
         ) as span:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/NumberReplacer, occurrence: 4, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -637,7 +637,7 @@
         if "pull_request" in payload.get("issue", {}):
             pr_url = payload["issue"]["pull_request"]["url"]
             # Extract PR number from URL
-            pr_number = int(pr_url.split("/")[-1])
+            pr_number = int(pr_url.split("/")[- 2])
         elif "pull_request" in payload:
             pr_number = payload["pull_request"]["number"]
         else:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7f6c09e7d010>
mock_github_client = <MagicMock name='github_client' id='140102016703792'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7f6c09e7d010>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
>           pr_number = int(pr_url.split("/")[- 2])
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           ValueError: invalid literal for int() with base 10: 'pulls'

stampbot/webhook_handler.py:640: ValueError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:08:46.319583Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:08:46.319583Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - ValueError: invalid literal for int() with base 10: 'pulls'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.43s
operator: core/NumberReplacer, occurrence: 5, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -637,7 +637,7 @@
         if "pull_request" in payload.get("issue", {}):
             pr_url = payload["issue"]["pull_request"]["url"]
             # Extract PR number from URL
-            pr_number = int(pr_url.split("/")[-1])
+            pr_number = int(pr_url.split("/")[- 0])
         elif "pull_request" in payload:
             pr_number = payload["pull_request"]["number"]
         else:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x79a78a2add90>
mock_github_client = <MagicMock name='github_client' id='133760502635824'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x79a78a2add90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
>           pr_number = int(pr_url.split("/")[- 0])
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           ValueError: invalid literal for int() with base 10: 'https:'

stampbot/webhook_handler.py:640: ValueError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:11:51.943069Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:11:51.943069Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - ValueError: invalid literal for int() with base 10: 'https:'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.43s
operator: core/NumberReplacer, occurrence: 6, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -701,7 +701,7 @@
                 set_span_ok(span)
                 return {"status": "ignored", "message": "No command found"}
 
-            command = command_match.group(1).lower()
+            command = command_match.group( 2).lower()
             add_span_attributes(span, {"chatops.command": command})
 
             if command == "help":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x754361361d90>
mock_github_client = <MagicMock name='github_client' id='128932273708336'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
>       result = await webhook_handler.handle_event("issue_comment", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:297: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:170: in handle_event
    result = await self._handle_pr_comment(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x754361361d90>
payload = {'action': 'created', 'issue': {'url': 'https://api.github.com/repos/octocat/hello-world/issues/42', 'repository_url':...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pr_comment(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle PR comment events for chatops.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        comment = payload.get("comment", {})
        comment_body = comment.get("body", "")
    
        # Security: limit comment length to prevent DoS
        if len(comment_body) > MAX_COMMENT_LENGTH:
            return {"status": "ignored", "message": "Comment too long"}
    
        comment_body = comment_body.lower().strip()
    
        # Check if bot is mentioned
        if "@stampbot" not in comment_body:
            return {"status": "ignored", "message": "Bot not mentioned"}
    
        # Extract PR info
        if "pull_request" in payload.get("issue", {}):
            pr_url = payload["issue"]["pull_request"]["url"]
            # Extract PR number from URL
            pr_number = int(pr_url.split("/")[-1])
        elif "pull_request" in payload:
            pr_number = payload["pull_request"]["number"]
        else:
            return {"status": "error", "message": "Not a PR comment"}
    
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
        commenter = comment.get("user", {}).get("login", "unknown")
    
        with create_span(
            "webhook.handle_chatops",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number,
                "chatops.commenter": commenter,
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in comment event")
                add_span_attributes(span, {"chatops.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                add_span_attributes(
                    span,
                    {
                        "chatops.result": "config_error",
                        "chatops.config_error": repo_config.config_error,
                    },
                )
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
            if not repo_config.chatops_enabled:
                add_span_attributes(span, {"chatops.result": "disabled"})
                set_span_ok(span)
                return {"status": "ignored", "message": "Chatops not enabled"}
    
            # Parse command
            command_match = re.search(r"@stampbot\s+(\w+)", comment_body)
            if not command_match:
                chatops_commands_total.labels(command="none", status="ignored").inc()
                add_span_attributes(span, {"chatops.result": "no_command"})
                set_span_ok(span)
                return {"status": "ignored", "message": "No command found"}
    
>           command = command_match.group( 2).lower()
                      ^^^^^^^^^^^^^^^^^^^^^^^
E           IndexError: no such group

stampbot/webhook_handler.py:704: IndexError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:04:19.704519Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:04:19.704957Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:04:19.704519Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:04:19.704957Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - IndexError: no such group
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.42s
operator: core/NumberReplacer, occurrence: 7, definition_name: _handle_pr_comment
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -701,7 +701,7 @@
                 set_span_ok(span)
                 return {"status": "ignored", "message": "No command found"}
 
-            command = command_match.group(1).lower()
+            command = command_match.group( 0).lower()
             add_span_attributes(span, {"chatops.command": command})
 
             if command == "help":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..................F
=================================== FAILURES ===================================
__________ test_chatops_approve_without_head_skips_duplicate_cleanup ___________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c8bcccb1d90>
mock_github_client = <MagicMock name='github_client' id='136939896216880'>

    @pytest.mark.asyncio
    async def test_chatops_approve_without_head_skips_duplicate_cleanup(
        webhook_handler, mock_github_client
    ):
        """Test no dismissal happens when the head is unknown, even with several approvals."""
        payload = load_fixture("issue_comment_approve")
        mock_github_client.get_pr_head_sha.return_value = None
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],
            [
                {"id": 1, "state": "APPROVED", "commit_id": "a"},
                {"id": 2, "state": "APPROVED", "commit_id": "b"},
            ],
        ]
    
        result = await webhook_handler.handle_event("issue_comment", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:299: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "issue_comment", "action": "created"}, "event": "Received issue_comment event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:20:02.630293Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:20:02.630713Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'issue_comment', 'action': 'created'}, 'event': 'Received issue_comment event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:20:02.630293Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:20:02.630713Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_chatops_approve_without_head_skips_duplicate_cleanup - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 378 passed, 3 deselected, 1 warning in 2.44s
operator: core/NumberReplacer, occurrence: 8, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1084,7 +1084,7 @@
                     # this head, and leaves the oldest of them in place.
                     review_ids = self._active_approvals_for_head(
                         reviews, head_sha, proven_only=True
-                    )[1:]
+                    )[ 2:]
                 else:
                     review_ids = self._active_approvals_for_head(reviews, head_sha)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7646815b3df0>
mock_github_client = <MagicMock name='github_client' id='130045211873296'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='130045211876992'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:33:05.032066Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:33:05.032494Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:33:05.032707Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:33:05.032066Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:33:05.032494Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:33:05.032707Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.44s
operator: core/NumberReplacer, occurrence: 9, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1084,7 +1084,7 @@
                     # this head, and leaves the oldest of them in place.
                     review_ids = self._active_approvals_for_head(
                         reviews, head_sha, proven_only=True
-                    )[1:]
+                    )[ 0:]
                 else:
                     review_ids = self._active_approvals_for_head(reviews, head_sha)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7c5474f16520>
mock_github_client = <MagicMock name='github_client' id='136702203166208'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_not_called()

tests/test_webhook_handler.py:118: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='136702203445664'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to not have been called. Called 1 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 777, 'Duplicate Stampbot approval')].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:05.646427Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:09:05.646893Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:05.647088Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:05.647707Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:05.647879Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "existing_review_ids": [777]}, "event": "PR #42 in octocat/hello-world already has active approval for current head, skipping", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:09:05.648036Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:05.646427Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:09:05.646893Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:05.647088Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:05.647707Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:05.647879Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:974 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'existing_review_ids': [777]}, 'event': 'PR #42 in octocat/hello-world already has active approval for current head, skipping', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:09:05.648036Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'dismiss_approval' to not have been called. Called 1 times.
Calls: [call(12345, 'octocat/hello-world', 42, 777, 'Duplicate Stampbot approval')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.44s
operator: core/RemoveDecorator, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -48,9 +48,6 @@
     REFRESH = "refresh"  # approve again only when a prior approval is stale
     DISMISS = "dismiss"
     NONE = "none"
-
-
-@dataclass(frozen=True)
 class PullRequestEvent:
     """The parts of a pull request webhook that policy decisions read."""
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x78b49743d0f0>
mock_github_client = <MagicMock name='github_client' id='132717049156944'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:161: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x78b49743d0f0>
payload = {'action': 'opened', 'number': 42, 'pull_request': {'url': 'https://api.github.com/repos/octocat/hello-world/pulls/42'...ository': {'id': 12345678, 'node_id': 'R_kgtest', 'name': 'hello-world', 'full_name': 'octocat/hello-world', ...}, ...}

    async def _handle_pull_request(self, payload: dict[str, Any]) -> dict[str, Any]:
        """Handle pull request events.
    
        Args:
            payload: Event payload
    
        Returns:
            Response dictionary
        """
        action = payload.get("action")
        pr = payload.get("pull_request", {})
        pr_number = pr.get("number")
        repo = payload.get("repository", {})
        repo_full_name = repo.get("full_name")
        repo_default_branch = repo.get("default_branch") or "main"
        repo_owner = repo.get("owner", {})
        owner_login = repo_owner.get("login")
        owner_type = repo_owner.get("type")
        installation_id = payload.get("installation", {}).get("id")
    
        with create_span(
            "webhook.handle_pull_request",
            {
                "github.repo": repo_full_name or "unknown",
                "github.pr_number": pr_number or 0,
                "github.action": action or "unknown",
            },
        ) as span:
            if not all([pr_number, repo_full_name, installation_id]):
                logger.warning("Missing required fields in PR event")
                add_span_attributes(span, {"webhook.result": "missing_fields"})
                set_span_ok(span)
                return {"status": "error", "message": "Missing required fields"}
    
            if action not in PULL_REQUEST_ACTIONS:
                # Edits, review requests, closes, and similar actions never change
                # review state, so they must not cost a policy read.
                add_span_attributes(span, {"webhook.result": "action_ignored"})
                set_span_ok(span)
                return {"status": "ignored", "message": f"Action {action} not handled"}
    
            repo_config = await self._policy.get(
                installation_id,
                repo_full_name,
                repo_default_branch,
                owner_login,
                owner_type,
            )
    
            if repo_config.config_error:
                logger.warning(
                    "Invalid Stampbot configuration for %s: %s",
                    repo_full_name,
                    repo_config.config_error,
                    extra={"repo": repo_full_name, "error": repo_config.config_error},
                )
                add_span_attributes(
                    span,
                    {
                        "webhook.result": "config_error",
                        "webhook.config_error": repo_config.config_error,
                    },
                )
    
                if action == "opened":
                    await run_in_threadpool(
                        github_client.create_pr_review_comment,
                        installation_id,
                        repo_full_name,
                        pr_number,
                        (
                            "Stampbot configuration error:\n\n"
                            f"{repo_config.config_error}\n\n"
                            "Please fix the configuration to re-enable automation."
                        ),
                    )
    
                set_span_ok(span)
                return {
                    "status": "error",
                    "message": "Invalid repository configuration",
                }
    
>           event = PullRequestEvent(
                action=action,
                installation_id=installation_id,
                repo_full_name=repo_full_name,
                pr_number=pr_number,
                owner_login=owner_login,
                pr=pr,
                event_label=payload.get("label", {}).get("name"),
                repo_config=repo_config,
            )
E           TypeError: PullRequestEvent() takes no arguments

stampbot/webhook_handler.py:263: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:33:21.864047Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:33:21.864694Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:153 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:33:21.864047Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:33:21.864694Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: PullRequestEvent() takes no arguments
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.41s
operator: core/RemoveDecorator, occurrence: 1, definition_name: PullRequestEvent
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -62,8 +62,6 @@
     pr: dict[str, Any]
     event_label: str | None
     repo_config: RepoConfig
-
-    @property
     def labels(self) -> list[str]:
         """Names of the labels currently on the pull request."""
         return [label["name"] for label in self.pr.get("labels", [])]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7eed766a50f0>
mock_github_client = <MagicMock name='github_client' id='139558379541840'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
>       result = await webhook_handler.handle_event("pull_request", payload)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_webhook_handler.py:75: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:162: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:275: in _handle_pull_request
    decision, label = self._decide(event)
                      ^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:312: in _decide
    label = self._find_approval_label(event.labels, config)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.webhook_handler.WebhookHandler object at 0x7eed766a50f0>
labels = <bound method PullRequestEvent.labels of PullRequestEvent(action='opened', installation_id=12345, repo_full_name='octo...etions': 10, 'changed_files': 3}, event_label=None, repo_config=<stampbot.config.RepoConfig object at 0x7eed765f3710>)>
repo_config = <stampbot.config.RepoConfig object at 0x7eed765f3710>

    def _find_approval_label(self, labels: list[str], repo_config: RepoConfig) -> str | None:
        """Find the first configured approval label present on the PR.
    
        Args:
            labels: Current PR labels.
            repo_config: Repository configuration.
    
        Returns:
            Matching approval label, if present.
        """
        for label in repo_config.approval_labels:
>           if label in labels:
               ^^^^^^^^^^^^^^^
E           TypeError: argument of type 'method' is not a container or iterable

stampbot/webhook_handler.py:344: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:57:28.807818Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:57:28.808474Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:154 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:57:28.807818Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:57:28.808474Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - TypeError: argument of type 'method' is not a container or iterable
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.43s
operator: core/RemoveDecorator, occurrence: 2, definition_name: PullRequestEvent
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -67,8 +67,6 @@
     def labels(self) -> list[str]:
         """Names of the labels currently on the pull request."""
         return [label["name"] for label in self.pr.get("labels", [])]
-
-    @property
     def head_sha(self) -> str | None:
         """Current head commit, when GitHub included it."""
         sha = self.pr.get("head", {}).get("sha")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
.F
=================================== FAILURES ===================================
_______________ test_pr_labeled_at_creation_approves_once[False] _______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7582246d6520>
mock_github_client = <MagicMock name='github_client' id='129201838718464'>
labeled_first = False

    @pytest.mark.asyncio
    @pytest.mark.parametrize("labeled_first", [False, True])
    async def test_pr_labeled_at_creation_approves_once(
        webhook_handler, mock_github_client, labeled_first
    ):
        """Test the opened and labeled events for one new pull request yield one approval.
    
        GitHub delivers the two events in either order. When the second event can
        see the first approval, the existing-approval check stops it.
        """
        opened = load_fixture("pr_opened_with_autoapprove_label")
        labeled = load_fixture("pr_labeled_autoapprove")
        events = [labeled, opened] if labeled_first else [opened, labeled]
        _remember_approval(mock_github_client)
    
        results = [await webhook_handler.handle_event("pull_request", e) for e in events]
    
        assert [r["status"] for r in results] == ["success", "success"]
>       mock_github_client.approve_pr.assert_called_once()

tests/test_webhook_handler.py:117: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.approve_pr' id='129201838709056'>

    def assert_called_once(self):
        """assert that the mock was called only once.
        """
        if not self.call_count == 1:
            msg = ("Expected '%s' to have been called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
E           Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
E            call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].

/usr/lib/python3.14/unittest/mock.py:965: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:13:28.948196Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T03:13:28.948695Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:13:28.948902Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:13:28.949422Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:13:28.949597Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:154 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:13:28.948196Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T03:13:28.948695Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:440 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:13:28.948902Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:154 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:13:28.949422Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:440 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:13:28.949597Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_labeled_at_creation_approves_once[False] - AssertionError: Expected 'approve_pr' to have been called once. Called 2 times.
Calls: [call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)'),
 call(12345, 'octocat/hello-world', 42, 'Auto-approved by Stampbot (label: autoapprove)')].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 361 passed, 3 deselected, 1 warning in 2.43s
operator: core/RemoveDecorator, occurrence: 3, definition_name: WebhookHandler
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -82,8 +82,6 @@
         """Initialize webhook handler (lazy initialization)."""
         self._webhook_secret: bytes | None = None
         self._policy = RepoPolicyResolver(repo_config_cache_seconds())
-
-    @property
     def webhook_secret(self) -> bytes:
         """Get webhook secret, initializing if needed.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7b9edcb7a9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:509: in webhook
    if not webhook_handler.verify_signature(body, x_hub_signature_256):
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:116: in verify_signature
    "sha256=" + hmac.new(self.webhook_secret, payload, hashlib.sha256).hexdigest()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/hmac.py:218: in new
    return HMAC(key, msg, digestmod)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <hmac.HMAC object at 0x7b9edb3c4c70>
key = <bound method WebhookHandler.webhook_secret of <stampbot.webhook_handler.WebhookHandler object at 0x7b9edbd2ef90>>
msg = b'{"test": "data"}', digestmod = <built-in function openssl_sha256>

    def __init__(self, key, msg=None, digestmod=''):
        """Create a new HMAC object.
    
        key: bytes or buffer, key for the keyed hash object.
        msg: bytes or buffer, Initial input for the hash or None.
        digestmod: A hash name suitable for hashlib.new(). *OR*
                   A hashlib constructor returning a new hash object. *OR*
                   A module supporting PEP 247.
    
                   Required as of 3.8, despite its position after the optional
                   msg argument.  Passing it as a keyword argument is
                   recommended, though not required for legacy API reasons.
        """
    
        if not isinstance(key, (bytes, bytearray)):
>           raise TypeError(f"key: expected bytes or bytearray, "
                            f"but got {type(key).__name__!r}")
E           TypeError: key: expected bytes or bytearray, but got 'method'

/usr/lib/python3.14/hmac.py:71: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:21:30.522324Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: key: expected bytes or bytearray, but got 'method'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.24s
operator: core/ZeroIterationForLoop, occurrence: 0, definition_name: _find_approval_label
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -342,7 +342,7 @@
         Returns:
             Matching approval label, if present.
         """
-        for label in repo_config.approval_labels:
+        for label in []:
             if label in labels:
                 return label
         return None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
F
=================================== FAILURES ===================================
____________________ test_pr_opened_with_autoapprove_label _____________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7e04235510f0>
mock_github_client = <MagicMock name='github_client' id='138556259636560'>

    @pytest.mark.asyncio
    async def test_pr_opened_with_autoapprove_label(webhook_handler, mock_github_client):
        """Test PR opened with autoapprove label triggers approval."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
>       assert result["status"] == "success"
E       AssertionError: assert 'ignored' == 'success'
E         
E         - success
E         + ignored

tests/test_webhook_handler.py:77: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:20:18.481463Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:20:18.482122Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:20:18.481463Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:20:18.482122Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_with_autoapprove_label - AssertionError: assert 'ignored' == 'success'
  
  - success
  + ignored
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 360 passed, 3 deselected, 1 warning in 2.42s
operator: core/ZeroIterationForLoop, occurrence: 1, definition_name: _warn_missing_approval_labels
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -522,7 +522,7 @@
             event: Pull request event
         """
         present = event.labels
-        for label in event.repo_config.approval_labels:
+        for label in []:
             if label in present:
                 continue
             label_exists = await run_in_threadpool(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
......................F
=================================== FAILURES ===================================
__________________ test_pr_opened_missing_label_logs_warning ___________________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x74e80fdafe70>
mock_github_client = <MagicMock name='github_client' id='128540137388896'>

    @pytest.mark.asyncio
    async def test_pr_opened_missing_label_logs_warning(webhook_handler, mock_github_client):
        """Test missing approval label triggers label check after the review decision."""
        payload = load_fixture("pr_opened_with_autoapprove_label")
        mock_github_client.repo_has_label.return_value = False
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        # The label on the pull request exists by definition; only "stamp" is checked.
>       mock_github_client.repo_has_label.assert_called_once_with(12345, "octocat/hello-world", "stamp")

tests/test_webhook_handler.py:352: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.repo_has_label' id='128540137374448'>
args = (12345, 'octocat/hello-world', 'stamp'), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "opened"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:12:36.304890Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:12:36.305298Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:12:36.305484Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'opened'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:12:36.304890Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:12:36.305298Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:12:36.305484Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_missing_label_logs_warning - AssertionError: Expected 'repo_has_label' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 382 passed, 3 deselected, 1 warning in 2.45s
operator: core/ZeroIterationForLoop, occurrence: 2, definition_name: _dismiss_approvals
--- mutation diff ---
--- a/stampbot/webhook_handler.py
+++ b/stampbot/webhook_handler.py
@@ -1103,7 +1103,7 @@
 
                 # Dismiss each review
                 success = True
-                for review_id in review_ids:
+                for review_id in []:
                     result = await run_in_threadpool(
                         github_client.dismiss_approval,
                         installation_id,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
...F
=================================== FAILURES ===================================
_____________ test_pr_opened_and_labeled_race_leaves_one_approval ______________

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x7cda3efa7df0>
mock_github_client = <MagicMock name='github_client' id='137276823179280'>

    @pytest.mark.asyncio
    async def test_pr_opened_and_labeled_race_leaves_one_approval(webhook_handler, mock_github_client):
        """Test two replicas that both approved converge on one active approval.
    
        Neither replica saw the other's review before approving. After approving,
        each re-reads the reviews and dismisses every approval of the head except
        the oldest, so both reach the same answer.
        """
        payload = load_fixture("pr_labeled_autoapprove")
        mock_github_client.find_bot_approval_reviews.side_effect = [
            [],  # pre-check: nothing yet
            [  # post-approval scan: both replicas have posted
                {"id": 200, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 100, "state": "APPROVED", "commit_id": "abc123def456"},
                {"id": 50, "state": "APPROVED", "commit_id": "oldsha"},  # stale, not a duplicate
                {"id": 60, "state": "DISMISSED", "commit_id": "abc123def456"},
            ],
        ]
    
        result = await webhook_handler.handle_event("pull_request", payload)
    
        assert result["status"] == "success"
        mock_github_client.approve_pr.assert_called_once()
>       mock_github_client.dismiss_approval.assert_called_once_with(
            12345, "octocat/hello-world", 42, 200, "Duplicate Stampbot approval"
        )

tests/test_webhook_handler.py:144: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='github_client.dismiss_approval' id='137276823182976'>
args = (12345, 'octocat/hello-world', 42, 200, 'Duplicate Stampbot approval')
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:02:43.469942Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "org_repo": null}, "event": "No stampbot.toml found in octocat/hello-world, using defaults", "logger": "stampbot.repo_policy", "level": "info", "timestamp": "2026-11-04T04:02:43.470354Z", "_logger": "<_FixedFindCallerLogger stampbot.repo_policy (INFO)>", "_name": "info"}
{"extra": {"repo": "octocat/hello-world", "pr_number": 42, "label": "autoapprove"}, "event": "PR #42 has approval label: autoapprove", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:02:43.470542Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:02:43.469942Z'}
INFO     stampbot.repo_policy:repo_policy.py:160 {'extra': {'repo': 'octocat/hello-world', 'org_repo': None}, 'event': 'No stampbot.toml found in octocat/hello-world, using defaults', 'logger': 'stampbot.repo_policy', 'level': 'info', 'timestamp': '2026-11-04T04:02:43.470354Z'}
INFO     stampbot.webhook_handler:webhook_handler.py:442 {'extra': {'repo': 'octocat/hello-world', 'pr_number': 42, 'label': 'autoapprove'}, 'event': 'PR #42 has approval label: autoapprove', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:02:43.470542Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pr_opened_and_labeled_race_leaves_one_approval - AssertionError: Expected 'dismiss_approval' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 363 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE - 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -397,7 +397,7 @@
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
-            self._bot_login_value = (login, now + BOT_LOGIN_TTL)
+            self._bot_login_value = (login, now - BOT_LOGIN_TTL)
             return login
 
     def approve_pr(
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x77c77669b4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
            assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
    
            # The App slug is read once and reused for later review scans, and
            # each operation builds exactly one client on the shared credentials.
            client.find_bot_approval_reviews(123456, "owner/repo", 43)
>           mock_integration.get_app.assert_called_once_with()

tests/test_github_client.py:1489: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='GithubIntegration().get_app' id='131698539470800'>, args = ()
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_app' to be called once. Called 2 times.
E           Calls: [call(), call()].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: Expected 'get_app' to be called once. Called 2 times.
Calls: [call(), call()].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE * 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -397,7 +397,7 @@
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
-            self._bot_login_value = (login, now + BOT_LOGIN_TTL)
+            self._bot_login_value = (login, now * BOT_LOGIN_TTL)
             return login
 
     def approve_pr(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE / 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x7c398bc58d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-341/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7c3988418320>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
>                   pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE / 1)
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E                   TypeError: argument should be integer or None, not 'float'

stampbot/github_client.py:241: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - TypeError: argument should be integer or None, not 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -397,7 +397,7 @@
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
-            self._bot_login_value = (login, now + BOT_LOGIN_TTL)
+            self._bot_login_value = (login, now / BOT_LOGIN_TTL)
             return login
 
     def approve_pr(
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x775e212ff4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
            assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
    
            # The App slug is read once and reused for later review scans, and
            # each operation builds exactly one client on the shared credentials.
            client.find_bot_approval_reviews(123456, "owner/repo", 43)
>           mock_integration.get_app.assert_called_once_with()

tests/test_github_client.py:1489: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='GithubIntegration().get_app' id='131246136238032'>, args = ()
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_app' to be called once. Called 2 times.
E           Calls: [call(), call()].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: Expected 'get_app' to be called once. Called 2 times.
Calls: [call(), call()].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE // 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -397,7 +397,7 @@
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
-            self._bot_login_value = (login, now + BOT_LOGIN_TTL)
+            self._bot_login_value = (login, now // BOT_LOGIN_TTL)
             return login
 
     def approve_pr(
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7834f55774d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
            assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
    
            # The App slug is read once and reused for later review scans, and
            # each operation builds exactly one client on the shared credentials.
            client.find_bot_approval_reviews(123456, "owner/repo", 43)
>           mock_integration.get_app.assert_called_once_with()

tests/test_github_client.py:1489: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='GithubIntegration().get_app' id='132168820498384'>, args = ()
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_app' to be called once. Called 2 times.
E           Calls: [call(), call()].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: Expected 'get_app' to be called once. Called 2 times.
Calls: [call(), call()].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE % 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x72ad96b48d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-1352/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x72ad9376c410>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE % 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -397,7 +397,7 @@
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
-            self._bot_login_value = (login, now + BOT_LOGIN_TTL)
+            self._bot_login_value = (login, now % BOT_LOGIN_TTL)
             return login
 
     def approve_pr(
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x70e64834b4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
            assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
    
            # The App slug is read once and reused for later review scans, and
            # each operation builds exactly one client on the shared credentials.
            client.find_bot_approval_reviews(123456, "owner/repo", 43)
>           mock_integration.get_app.assert_called_once_with()

tests/test_github_client.py:1489: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='GithubIntegration().get_app' id='124134301704144'>, args = ()
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_app' to be called once. Called 2 times.
E           Calls: [call(), call()].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: Expected 'get_app' to be called once. Called 2 times.
Calls: [call(), call()].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE ** 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -397,7 +397,7 @@
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
-            self._bot_login_value = (login, now + BOT_LOGIN_TTL)
+            self._bot_login_value = (login, now ** BOT_LOGIN_TTL)
             return login
 
     def approve_pr(
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x771fae5874d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:08:30.627990Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: (34, 'Numerical result out of range') [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: (34, 'Numerical result out of range')", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:08:30.627990Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE >> 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -397,7 +397,7 @@
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
-            self._bot_login_value = (login, now + BOT_LOGIN_TTL)
+            self._bot_login_value = (login, now >> BOT_LOGIN_TTL)
             return login
 
     def approve_pr(
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x78fc2218b4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:11:51.699007Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'int' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'int'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'int'"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'int'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:11:51.699007Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE << 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -397,7 +397,7 @@
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
-            self._bot_login_value = (login, now + BOT_LOGIN_TTL)
+            self._bot_login_value = (login, now << BOT_LOGIN_TTL)
             return login
 
     def approve_pr(
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x74c31e4fb4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:50:09.182846Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'int' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'int'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'int'"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'int'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:50:09.182846Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE | 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -397,7 +397,7 @@
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
-            self._bot_login_value = (login, now + BOT_LOGIN_TTL)
+            self._bot_login_value = (login, now | BOT_LOGIN_TTL)
             return login
 
     def approve_pr(
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x737254c734d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:39:14.548093Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'int' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'int'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'int'"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'int'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:39:14.548093Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE & 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x7feebba50d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-529/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7feeb8674320>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE & 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.38s
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -397,7 +397,7 @@
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
-            self._bot_login_value = (login, now + BOT_LOGIN_TTL)
+            self._bot_login_value = (login, now & BOT_LOGIN_TTL)
             return login
 
     def approve_pr(
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x75f1ca9134d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:49:59.490822Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'int' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'int'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'int'"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'int'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:49:59.490822Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE ^ 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -397,7 +397,7 @@
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
-            self._bot_login_value = (login, now + BOT_LOGIN_TTL)
+            self._bot_login_value = (login, now ^ BOT_LOGIN_TTL)
             return login
 
     def approve_pr(
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x791f452774d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:16:04.196669Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'int' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'int'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'int'"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'int'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:16:04.196669Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in self._rate_limit_installations + live:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x71f027b7e350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='125276214499312'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:352: in _get_installation_client
    installation_auth = self._installation_auth(installation_id)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:313: in _installation_auth
    self._prune_rate_limit_metrics()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x71f024841090>

    def _prune_rate_limit_metrics(self) -> None:
        """Drop rate-limit gauge label sets for installations without live credentials.
    
        Prometheus keeps a child series for every label value ever set. Pruning
        whenever new credentials are created bounds the series count by the
        credential cache size, so installation churn cannot grow it without
        limit. Callers hold ``_installation_auths_lock``.
        """
        live = {str(installation_id) for installation_id in self._installation_auths}
>       for stale in self._rate_limit_installations + live:
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: unsupported operand type(s) for +: 'set' and 'set'

stampbot/github_client.py:325: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for +: 'set' and 'set'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 1, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -439,7 +439,7 @@
                     event="APPROVE",
                 )
 
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -459,7 +459,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 3, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -508,7 +508,7 @@
                 pr = repo.get_pull(pr_number)
                 head_sha: str = pr.head.sha
 
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 4, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -520,7 +520,7 @@
                 return head_sha
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -599,7 +599,7 @@
                         },
                     )
 
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 6, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -620,7 +620,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 7, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -682,7 +682,7 @@
 
                 content = _get_repo_contents(repo, file_path, ref)
                 if isinstance(content, list):
-                    duration = time.time() - start_time
+                    duration = time.time() + start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 8, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -691,7 +691,7 @@
                     set_span_ok(span)
                     return None
 
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 9, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -703,7 +703,7 @@
                 return content.decoded_content.decode("utf-8")
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
                 if e.status == 404 and repo is not None:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -758,7 +758,7 @@
                 raise
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
                 add_span_attributes(span, {"github.result": "error"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 11, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -824,7 +824,7 @@
                             }
                         )
 
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -840,7 +840,7 @@
                 return bot_reviews
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -897,7 +897,7 @@
 
                 pr.create_review(body=message, event="COMMENT")
 
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 14, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -919,7 +919,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 15, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -974,7 +974,7 @@
 
                 issue.create_comment(message)
 
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 16, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -998,7 +998,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 17, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1052,7 +1052,7 @@
                 # The lazy client defers the request until the label is read.
                 repo.get_label(label_name).complete()
 
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 18, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1063,7 +1063,7 @@
                 return True
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.31s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 19, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1088,7 +1088,7 @@
                 return None
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 20, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1151,7 +1151,7 @@
 
                 has_permission = permission_index >= required_index
 
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 21, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1171,7 +1171,7 @@
                 return has_permission
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 22, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1262,7 +1262,7 @@
                         )
                         continue
 
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 23, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1282,7 +1282,7 @@
                 return member_teams
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() + start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in self._rate_limit_installations * live:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x747af0302350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='128071312873456'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:352: in _get_installation_client
    installation_auth = self._installation_auth(installation_id)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:313: in _installation_auth
    self._prune_rate_limit_metrics()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x747aed1d1130>

    def _prune_rate_limit_metrics(self) -> None:
        """Drop rate-limit gauge label sets for installations without live credentials.
    
        Prometheus keeps a child series for every label value ever set. Pruning
        whenever new credentials are created bounds the series count by the
        credential cache size, so installation churn cannot grow it without
        limit. Callers hold ``_installation_auths_lock``.
        """
        live = {str(installation_id) for installation_id in self._installation_auths}
>       for stale in self._rate_limit_installations * live:
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: unsupported operand type(s) for *: 'set' and 'set'

stampbot/github_client.py:325: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for *: 'set' and 'set'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 1, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -439,7 +439,7 @@
                     event="APPROVE",
                 )
 
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -459,7 +459,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 3, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -508,7 +508,7 @@
                 pr = repo.get_pull(pr_number)
                 head_sha: str = pr.head.sha
 
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 4, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -520,7 +520,7 @@
                 return head_sha
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -599,7 +599,7 @@
                         },
                     )
 
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 6, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -620,7 +620,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 7, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -682,7 +682,7 @@
 
                 content = _get_repo_contents(repo, file_path, ref)
                 if isinstance(content, list):
-                    duration = time.time() - start_time
+                    duration = time.time() * start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 8, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -691,7 +691,7 @@
                     set_span_ok(span)
                     return None
 
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 9, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -703,7 +703,7 @@
                 return content.decoded_content.decode("utf-8")
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
                 if e.status == 404 and repo is not None:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -758,7 +758,7 @@
                 raise
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
                 add_span_attributes(span, {"github.result": "error"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 11, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -824,7 +824,7 @@
                             }
                         )
 
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -840,7 +840,7 @@
                 return bot_reviews
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -897,7 +897,7 @@
 
                 pr.create_review(body=message, event="COMMENT")
 
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 14, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -919,7 +919,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 15, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -974,7 +974,7 @@
 
                 issue.create_comment(message)
 
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.43s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 16, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -998,7 +998,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 17, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1052,7 +1052,7 @@
                 # The lazy client defers the request until the label is read.
                 repo.get_label(label_name).complete()
 
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 18, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1063,7 +1063,7 @@
                 return True
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 19, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1088,7 +1088,7 @@
                 return None
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 20, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1151,7 +1151,7 @@
 
                 has_permission = permission_index >= required_index
 
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 21, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1171,7 +1171,7 @@
                 return has_permission
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 22, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1262,7 +1262,7 @@
                         )
                         continue
 
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 23, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1282,7 +1282,7 @@
                 return member_teams
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() * start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in self._rate_limit_installations / live:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x74b65e492350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='128326557898736'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:352: in _get_installation_client
    installation_auth = self._installation_auth(installation_id)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:313: in _installation_auth
    self._prune_rate_limit_metrics()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x74b65b129130>

    def _prune_rate_limit_metrics(self) -> None:
        """Drop rate-limit gauge label sets for installations without live credentials.
    
        Prometheus keeps a child series for every label value ever set. Pruning
        whenever new credentials are created bounds the series count by the
        credential cache size, so installation churn cannot grow it without
        limit. Callers hold ``_installation_auths_lock``.
        """
        live = {str(installation_id) for installation_id in self._installation_auths}
>       for stale in self._rate_limit_installations / live:
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: unsupported operand type(s) for /: 'set' and 'set'

stampbot/github_client.py:325: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for /: 'set' and 'set'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 1, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -439,7 +439,7 @@
                     event="APPROVE",
                 )
 
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -459,7 +459,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 3, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -508,7 +508,7 @@
                 pr = repo.get_pull(pr_number)
                 head_sha: str = pr.head.sha
 
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 4, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -520,7 +520,7 @@
                 return head_sha
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -599,7 +599,7 @@
                         },
                     )
 
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 6, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -620,7 +620,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 7, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -682,7 +682,7 @@
 
                 content = _get_repo_contents(repo, file_path, ref)
                 if isinstance(content, list):
-                    duration = time.time() - start_time
+                    duration = time.time() / start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 8, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -691,7 +691,7 @@
                     set_span_ok(span)
                     return None
 
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 9, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -703,7 +703,7 @@
                 return content.decoded_content.decode("utf-8")
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
                 if e.status == 404 and repo is not None:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -758,7 +758,7 @@
                 raise
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
                 add_span_attributes(span, {"github.result": "error"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 11, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -824,7 +824,7 @@
                             }
                         )
 
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -840,7 +840,7 @@
                 return bot_reviews
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -897,7 +897,7 @@
 
                 pr.create_review(body=message, event="COMMENT")
 
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 14, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -919,7 +919,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 15, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -974,7 +974,7 @@
 
                 issue.create_comment(message)
 
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 16, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -998,7 +998,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 17, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1052,7 +1052,7 @@
                 # The lazy client defers the request until the label is read.
                 repo.get_label(label_name).complete()
 
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 18, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1063,7 +1063,7 @@
                 return True
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 19, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1088,7 +1088,7 @@
                 return None
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 20, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1151,7 +1151,7 @@
 
                 has_permission = permission_index >= required_index
 
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 21, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1171,7 +1171,7 @@
                 return has_permission
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 22, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1262,7 +1262,7 @@
                         )
                         continue
 
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 23, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1282,7 +1282,7 @@
                 return member_teams
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() / start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.42s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in self._rate_limit_installations // live:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x7c0698e66350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='136367718930416'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:352: in _get_installation_client
    installation_auth = self._installation_auth(installation_id)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:313: in _installation_auth
    self._prune_rate_limit_metrics()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7c0695b49090>

    def _prune_rate_limit_metrics(self) -> None:
        """Drop rate-limit gauge label sets for installations without live credentials.
    
        Prometheus keeps a child series for every label value ever set. Pruning
        whenever new credentials are created bounds the series count by the
        credential cache size, so installation churn cannot grow it without
        limit. Callers hold ``_installation_auths_lock``.
        """
        live = {str(installation_id) for installation_id in self._installation_auths}
>       for stale in self._rate_limit_installations // live:
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: unsupported operand type(s) for //: 'set' and 'set'

stampbot/github_client.py:325: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for //: 'set' and 'set'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 1, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -439,7 +439,7 @@
                     event="APPROVE",
                 )
 
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -459,7 +459,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 3, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -508,7 +508,7 @@
                 pr = repo.get_pull(pr_number)
                 head_sha: str = pr.head.sha
 
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 4, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -520,7 +520,7 @@
                 return head_sha
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -599,7 +599,7 @@
                         },
                     )
 
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 6, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -620,7 +620,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 7, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -682,7 +682,7 @@
 
                 content = _get_repo_contents(repo, file_path, ref)
                 if isinstance(content, list):
-                    duration = time.time() - start_time
+                    duration = time.time() // start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 8, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -691,7 +691,7 @@
                     set_span_ok(span)
                     return None
 
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 9, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -703,7 +703,7 @@
                 return content.decoded_content.decode("utf-8")
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
                 if e.status == 404 and repo is not None:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -758,7 +758,7 @@
                 raise
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
                 add_span_attributes(span, {"github.result": "error"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.42s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 11, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -824,7 +824,7 @@
                             }
                         )
 
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -840,7 +840,7 @@
                 return bot_reviews
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -897,7 +897,7 @@
 
                 pr.create_review(body=message, event="COMMENT")
 
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 14, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -919,7 +919,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 15, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -974,7 +974,7 @@
 
                 issue.create_comment(message)
 
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 16, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -998,7 +998,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 17, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1052,7 +1052,7 @@
                 # The lazy client defers the request until the label is read.
                 repo.get_label(label_name).complete()
 
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 18, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1063,7 +1063,7 @@
                 return True
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.41s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 19, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1088,7 +1088,7 @@
                 return None
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 20, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1151,7 +1151,7 @@
 
                 has_permission = permission_index >= required_index
 
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 21, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1171,7 +1171,7 @@
                 return has_permission
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 22, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1262,7 +1262,7 @@
                         )
                         continue
 
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 23, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1282,7 +1282,7 @@
                 return member_teams
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() // start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in self._rate_limit_installations % live:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x737c6caee350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='126978181699568'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:352: in _get_installation_client
    installation_auth = self._installation_auth(installation_id)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:313: in _installation_auth
    self._prune_rate_limit_metrics()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x737c69611090>

    def _prune_rate_limit_metrics(self) -> None:
        """Drop rate-limit gauge label sets for installations without live credentials.
    
        Prometheus keeps a child series for every label value ever set. Pruning
        whenever new credentials are created bounds the series count by the
        credential cache size, so installation churn cannot grow it without
        limit. Callers hold ``_installation_auths_lock``.
        """
        live = {str(installation_id) for installation_id in self._installation_auths}
>       for stale in self._rate_limit_installations % live:
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: unsupported operand type(s) for %: 'set' and 'set'

stampbot/github_client.py:325: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for %: 'set' and 'set'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 1, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -439,7 +439,7 @@
                     event="APPROVE",
                 )
 
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.41s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -459,7 +459,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 3, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -508,7 +508,7 @@
                 pr = repo.get_pull(pr_number)
                 head_sha: str = pr.head.sha
 
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 4, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -520,7 +520,7 @@
                 return head_sha
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -599,7 +599,7 @@
                         },
                     )
 
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 6, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -620,7 +620,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 7, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -682,7 +682,7 @@
 
                 content = _get_repo_contents(repo, file_path, ref)
                 if isinstance(content, list):
-                    duration = time.time() - start_time
+                    duration = time.time() % start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 8, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -691,7 +691,7 @@
                     set_span_ok(span)
                     return None
 
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 9, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -703,7 +703,7 @@
                 return content.decoded_content.decode("utf-8")
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
                 if e.status == 404 and repo is not None:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -758,7 +758,7 @@
                 raise
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
                 add_span_attributes(span, {"github.result": "error"})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.42s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 11, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -824,7 +824,7 @@
                             }
                         )
 
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -840,7 +840,7 @@
                 return bot_reviews
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -897,7 +897,7 @@
 
                 pr.create_review(body=message, event="COMMENT")
 
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 14, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -919,7 +919,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 15, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -974,7 +974,7 @@
 
                 issue.create_comment(message)
 
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 16, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -998,7 +998,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 17, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1052,7 +1052,7 @@
                 # The lazy client defers the request until the label is read.
                 repo.get_label(label_name).complete()
 
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 18, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1063,7 +1063,7 @@
                 return True
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 19, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1088,7 +1088,7 @@
                 return None
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 20, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1151,7 +1151,7 @@
 
                 has_permission = permission_index >= required_index
 
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.30s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 21, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1171,7 +1171,7 @@
                 return has_permission
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 22, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1262,7 +1262,7 @@
                         )
                         continue
 
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 23, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1282,7 +1282,7 @@
                 return member_teams
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() % start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in self._rate_limit_installations ** live:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x70223acf6350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='123292264323056'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:352: in _get_installation_client
    installation_auth = self._installation_auth(installation_id)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:313: in _installation_auth
    self._prune_rate_limit_metrics()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7022379d9130>

    def _prune_rate_limit_metrics(self) -> None:
        """Drop rate-limit gauge label sets for installations without live credentials.
    
        Prometheus keeps a child series for every label value ever set. Pruning
        whenever new credentials are created bounds the series count by the
        credential cache size, so installation churn cannot grow it without
        limit. Callers hold ``_installation_auths_lock``.
        """
        live = {str(installation_id) for installation_id in self._installation_auths}
>       for stale in self._rate_limit_installations ** live:
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: unsupported operand type(s) for ** or pow(): 'set' and 'set'

stampbot/github_client.py:325: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for ** or pow(): 'set' and 'set'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.38s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 1, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -439,7 +439,7 @@
                     event="APPROVE",
                 )
 
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="success").inc()
 
........................................................................ [ 15%]
.............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________

self = <tests.test_github_client.TestApprovePR object at 0x7c93c5386ad0>

    def test_approve_pr_success(self):
        """Test successful PR approval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.approve_pr(123456, "owner/repo", 42)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:631: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:53:52.384866Z [error    ] Failed to approve PR #42 in owner/repo: (34, 'Numerical result out of range') [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:468 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}, 'event': "Failed to approve PR #42 in owner/repo: (34, 'Numerical result out of range')", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:53:52.384866Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 133 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -459,7 +459,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
 
........................................................................ [ 15%]
..............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________

self = <stampbot.github_client.GitHubAppClient object at 0x7e7144cd6c10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
>               pr.create_review(
                    body=comment,
                    event="APPROVE",
                )

stampbot/github_client.py:437: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().create_review' id='139024945674096'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestApprovePR object at 0x7e7147f92c10>

    def test_approve_pr_failure(self):
        """Test PR approval failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.create_review.side_effect = Exception("API Error")
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.approve_pr(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:668: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e7144cd6c10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
                pr.create_review(
                    body=comment,
                    event="APPROVE",
                )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                github_api_requests_total.labels(operation="approve", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "approved"})
                set_span_ok(span)
    
                logger.info(
                    f"Approved PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:462: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 3, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -508,7 +508,7 @@
                 pr = repo.get_pull(pr_number)
                 head_sha: str = pr.head.sha
 
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="success").inc()
 
........................................................................ [ 15%]
...............................................................F
=================================== FAILURES ===================================
________________ TestGetPRHeadSHA.test_get_pr_head_sha_success _________________

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x71eb555fed50>

    def test_get_pr_head_sha_success(self):
        """Test successful PR head SHA lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.head.sha = "current-head"
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_pr_head_sha(123456, "owner/repo", 42)
    
>           assert result == "current-head"
E           AssertionError: assert None == 'current-head'

tests/test_github_client.py:713: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:03:58.377383Z [error    ] Failed to get head SHA for PR #42 in owner/repo: (34, 'Numerical result out of range') [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:529 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}, 'event': "Failed to get head SHA for PR #42 in owner/repo: (34, 'Numerical result out of range')", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:03:58.377383Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_success - AssertionError: assert None == 'current-head'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 135 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 4, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -520,7 +520,7 @@
                 return head_sha
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
 
........................................................................ [ 15%]
................................................................F
=================================== FAILURES ===================================
_________ TestGetPRHeadSHA.test_get_pr_head_sha_returns_none_on_error __________

self = <stampbot.github_client.GitHubAppClient object at 0x7e43929237f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:507: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='138828683819200'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x7e4395c6ae90>

    def test_get_pr_head_sha_returns_none_on_error(self):
        """Test PR head SHA lookup returns None on API errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_pr_head_sha(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:745: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e43929237f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                head_sha: str = pr.head.sha
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                github_api_requests_total.labels(operation="get_pull", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.head_sha": head_sha})
                set_span_ok(span)
    
                return head_sha
    
            except Exception as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:523: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_returns_none_on_error - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 136 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -599,7 +599,7 @@
                         },
                     )
 
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="success").inc()
 
........................................................................ [ 15%]
.................................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________

self = <tests.test_github_client.TestDismissApproval object at 0x7a78bf12afd0>

    def test_dismiss_approval_success(self):
        """Test successful approval dismissal."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_review = Mock()
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.dismiss_approval(123456, "owner/repo", 42, 999)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:791: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:54:44.581843Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: (34, 'Numerical result out of range') [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}, 'event': "Failed to dismiss approval for PR #42 in owner/repo: (34, 'Numerical result out of range')", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:54:44.581843Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 137 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 6, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -620,7 +620,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
 
........................................................................ [ 15%]
...................................................................F
=================================== FAILURES ===================================
_ TestDismissApproval.test_dismiss_approval_422_with_review_still_approved_fails _

self = <stampbot.github_client.GitHubAppClient object at 0x796354a57bb0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
>                   review.dismiss(message)

stampbot/github_client.py:582: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='133467525633360'>
args = ('Duplicate',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 422 {"message": "Validation Failed"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestDismissApproval object at 0x796357e5d350>

    def test_dismiss_approval_422_with_review_still_approved_fails(self):
        """Test a 422 that leaves the review approved is reported as a failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "APPROVED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Validation Failed"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is False
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:864: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x796354a57bb0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
                    review.dismiss(message)
                except GithubException as e:
                    # GitHub answers 422 when a review is already dismissed,
                    # which two replicas cleaning up the same duplicate reach on
                    # purpose. 422 also covers other validation failures, so the
                    # review's state is read back before treating it as done.
                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
                        raise
                    logger.info(
                        "Review %d on PR #%d in %s was already dismissed",
                        review_id,
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "review_id": review_id,
                        },
                    )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                github_api_requests_total.labels(operation="dismiss", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "dismissed"})
                set_span_ok(span)
    
                logger.info(
                    f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                        "review_id": review_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:623: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_422_with_review_still_approved_fails - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 139 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 7, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -682,7 +682,7 @@
 
                 content = _get_repo_contents(repo, file_path, ref)
                 if isinstance(content, list):
-                    duration = time.time() - start_time
+                    duration = time.time() ** start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
                     )
........................................................................ [ 15%]
.......................................................................F [ 30%]

=================================== FAILURES ===================================
________ TestGetRepoFile.test_get_repo_file_returns_none_for_directory _________

self = <tests.test_github_client.TestGetRepoFile object at 0x783cfc193250>

    def test_get_repo_file_returns_none_for_directory(self):
        """Test that get_repo_file returns None when path is a directory."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Return a list (indicating directory)
            mock_repo = Mock()
            mock_repo.get_contents.return_value = [Mock(), Mock()]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "some/directory")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1021: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x783cf8b04910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'some/directory', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
>                   duration = time.time() ** start_time
                               ^^^^^^^^^^^^^^^^^^^^^^^^^
E                   OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:685: OverflowError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:22:24.432128Z [warning  ] GitHub could not read some/directory from owner/repo (OverflowError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}, 'event': 'GitHub could not read some/directory from owner/repo (OverflowError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:22:24.432128Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_for_directory - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 143 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 8, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -691,7 +691,7 @@
                     set_span_ok(span)
                     return None
 
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="success").inc()
 
........................................................................ [ 15%]
......................................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________

self = <tests.test_github_client.TestGetRepoFile object at 0x712c64187390>

    def test_get_repo_file_success(self):
        """Test successful file retrieval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_content = Mock()
            mock_content.decoded_content = b"file content"
            mock_repo = Mock()
            mock_repo.get_contents.return_value = mock_content
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/repo",
                "stampbot.toml",
                ref="main",
            )

tests/test_github_client.py:978: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x712c60e53f70>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = 'main'
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:694: OverflowError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:11:29.888567Z [warning  ] GitHub could not read stampbot.toml from owner/repo (OverflowError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/repo (OverflowError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:11:29.888567Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 9, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -703,7 +703,7 @@
                 return content.decoded_content.decode("utf-8")
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
                 if e.status == 404 and repo is not None:
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <stampbot.github_client.GitHubAppClient object at 0x72af3e5d8870>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='126096991105920'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x72af41b455b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x72af3e5d8870>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:706: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.51s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -758,7 +758,7 @@
                 raise
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
                 add_span_attributes(span, {"github.result": "error"})
........................................................................ [ 15%]
........................................................................ [ 30%]
.............F
=================================== FAILURES ===================================
____ TestGetRepoFile.test_get_repo_file_propagates_read_failures[failure2] _____

self = <stampbot.github_client.GitHubAppClient object at 0x7148b8d02e90>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='124557180011920'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               TimeoutError: GitHub read timed out

/usr/lib/python3.14/unittest/mock.py:1241: TimeoutError

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x7148bc409490>
failure = TimeoutError('GitHub read timed out')

    @pytest.mark.parametrize(
        "failure",
        [
            GithubException(403, {"message": "Forbidden"}, None),
            GithubException(500, {"message": "Error"}, None),
            TimeoutError("GitHub read timed out"),
        ],
    )
    def test_get_repo_file_propagates_read_failures(self, failure):
        """Test non-404 policy failures reach the handler's fail-closed path."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = failure
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(type(failure), match=str(failure)):
>               client.get_repo_file(123456, "owner/repo", "stampbot.toml")

tests/test_github_client.py:1410: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7148b8d02e90>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                not_found: NotFoundKind = "unconfirmed"
                if e.status == 404 and repo is not None:
                    not_found = _classify_not_found(
                        repo, ref, optional_repo=missing_repository_is_optional
                    )
                repository_is_optionally_missing = not_found == "repository"
                file_is_confirmed_missing = not_found == "file"
                if repository_is_optionally_missing or file_is_confirmed_missing:
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
    
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
    
                    if repository_is_optionally_missing:
                        logger.debug(
                            "Optional policy repository %s was not found",
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    else:
                        logger.debug(
                            "Policy file %s was not found in %s",
                            file_path,
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    return None
    
                github_api_requests_total.labels(operation="get_file", status="error").inc()
                add_span_attributes(span, {"github.result": "error"})
                set_span_error(span, e)
                logger.warning(
                    "GitHub could not read %s from %s (status %s)",
                    file_path,
                    repo_full_name,
                    e.status,
                    extra={
                        "repo": repo_full_name,
                        "file_path": file_path,
                        "installation_id": installation_id,
                    },
                )
                raise
    
            except Exception as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:761: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_read_failures[failure2] - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 157 passed, 3 deselected, 1 warning in 1.52s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 11, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -824,7 +824,7 @@
                             }
                         )
 
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x796831c8f4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:30:01.495005Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: (34, 'Numerical result out of range') [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: (34, 'Numerical result out of range')", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:30:01.495005Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -840,7 +840,7 @@
                 return bot_reviews
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............F
=================================== FAILURES ===================================
_ TestFindBotApprovalReviews.test_find_bot_approval_reviews_returns_none_on_error _

self = <stampbot.github_client.GitHubAppClient object at 0x73286dafb890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:809: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='126617476944224'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x732870f9f610>

    def test_find_bot_approval_reviews_returns_none_on_error(self):
        """Test that a listing failure is reported as None, distinct from no reviews."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1523: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x73286dafb890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                bot_user = self._bot_login()
    
                bot_reviews: list[BotReview] = []
                for review in pr.get_reviews():
                    if review.user.login == bot_user and review.state in (
                        "APPROVED",
                        "DISMISSED",
                    ):
                        bot_reviews.append(
                            {
                                "id": review.id,
                                "state": review.state,
                                "commit_id": getattr(review, "commit_id", None),
                            }
                        )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                    duration
                )
                github_api_requests_total.labels(operation="find_reviews", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span, {"github.reviews_found": len(bot_reviews), "github.bot_user": bot_user}
                )
                set_span_ok(span)
    
                return bot_reviews
    
            except Exception as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:843: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_returns_none_on_error - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 159 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -897,7 +897,7 @@
 
                 pr.create_review(body=message, event="COMMENT")
 
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x75fb1557fed0>

    def test_create_pr_review_comment_success(self):
        """Test successful review comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2098: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:06:09.178332Z [warning  ] Failed to post config error review for PR #42 in owner/repo: (34, 'Numerical result out of range') [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:928 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}, 'event': "Failed to post config error review for PR #42 in owner/repo: (34, 'Numerical result out of range')", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:06:09.178332Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 173 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 14, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -919,7 +919,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________

self = <stampbot.github_client.GitHubAppClient object at 0x73f58e42d270>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               pr = repo.get_pull(pr_number)
                     ^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:896: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull' id='127498490229632'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x73f5900a0050>

    def test_create_pr_review_comment_failure(self):
        """Test review comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_pull.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )

tests/test_github_client.py:2133: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x73f58e42d270>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                pr.create_review(body=message, event="COMMENT")
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                github_api_requests_total.labels(operation="comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted config error review on PR #%d in %s",
                    pr_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:922: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 174 passed, 3 deselected, 1 warning in 1.54s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 15, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -974,7 +974,7 @@
 
                 issue.create_comment(message)
 
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_success ___________

self = <tests.test_github_client.TestCreateIssueComment object at 0x79d2c6ba0190>

    def test_create_issue_comment_success(self):
        """Test successful issue comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_issue = Mock()
            mock_repo = Mock()
            mock_repo.get_issue.return_value = mock_issue
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2187: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:26:12.892464Z [warning  ] Failed to post issue comment on #42 in owner/repo: (34, 'Numerical result out of range') [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1009 {'extra': {'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}, 'event': "Failed to post issue comment on #42 in owner/repo: (34, 'Numerical result out of range')", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:26:12.892464Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 175 passed, 3 deselected, 1 warning in 1.50s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 16, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -998,7 +998,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_failure ___________

self = <stampbot.github_client.GitHubAppClient object at 0x7ce8c7231a90>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               issue = repo.get_issue(issue_number)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:973: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_issue' id='137339214853328'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreateIssueComment object at 0x7ce8c8e982d0>

    def test_create_issue_comment_failure(self):
        """Test issue comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_issue.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )

tests/test_github_client.py:2223: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7ce8c7231a90>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                issue = repo.get_issue(issue_number)
    
                issue.create_comment(message)
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                    duration
                )
                github_api_requests_total.labels(operation="issue_comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted issue comment on #%d in %s",
                    issue_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "issue_number": issue_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:1001: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_failure - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 176 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 17, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1052,7 +1052,7 @@
                 # The lazy client defers the request until the label is read.
                 repo.get_label(label_name).complete()
 
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x794b3631fc50>

    def test_repo_has_label_true(self):
        """Test repo_has_label returns True when label exists."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.return_value = Mock()
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "autoapprove")
    
>           assert result is True
E           assert None is True

tests/test_github_client.py:1937: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 18, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1063,7 +1063,7 @@
                 return True
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <stampbot.github_client.GitHubAppClient object at 0x77d507f28c30>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='131756845874352'>
args = ('missing',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x77d509c83d90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "missing")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1975: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x77d507f28c30>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:1066: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 19, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1088,7 +1088,7 @@
                 return None
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________

self = <stampbot.github_client.GitHubAppClient object at 0x7c7ff4210c30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='136888997556784'>
args = ('autoapprove',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x7c7ff5ff1e00>

    def test_repo_has_label_error(self):
        """Test repo_has_label returns None on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "autoapprove")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:2049: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7c7ff4210c30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="failure").inc()
    
                if e.status == 404:
                    add_span_attributes(span, {"github.label_found": False})
                    set_span_ok(span)
                    return False
    
                set_span_error(span, e)
                logger.debug(
                    "Could not verify label %s in %s: %s",
                    label_name,
                    repo_full_name,
                    _sanitize_error(e),
                    extra={
                        "repo": repo_full_name,
                        "label": label_name,
                        "installation_id": installation_id,
                        "error": _sanitize_error(e),
                    },
                )
                return None
    
            except Exception as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:1091: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 172 passed, 3 deselected, 1 warning in 1.52s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 20, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1151,7 +1151,7 @@
 
                 has_permission = permission_index >= required_index
 
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________

self = <tests.test_github_client.TestUserHasPermission object at 0x7af139a53750>

    def test_user_has_permission_true(self):
        """Test required permission satisfied returns True."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.return_value = "write"
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "alice", "write")
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:1566: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:16:22.924459Z [warning  ] Failed to check collaborator permission for alice in owner/repo: (34, 'Numerical result out of range') [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1182 {'extra': {'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}, 'event': "Failed to check collaborator permission for alice in owner/repo: (34, 'Numerical result out of range')", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:16:22.924459Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 160 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 21, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1171,7 +1171,7 @@
                 return has_permission
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________

self = <stampbot.github_client.GitHubAppClient object at 0x7738a3767c50>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
>               permission = repo.get_collaborator_permission(username)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_collaborator_permission' id='131085240134560'>
args = ('carol',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestUserHasPermission object at 0x7738aaf49810>

    def test_user_has_permission_error(self):
        """Test permission check returns False on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1637: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7738a3767c50>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
                permission = repo.get_collaborator_permission(username)
                permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
                try:
                    permission_index = permission_order.index(permission)
                    required_index = permission_order.index(required_permission)
                except ValueError:
                    permission_index = -1
                    required_index = 99
    
                has_permission = permission_index >= required_index
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_permission").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_permission", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.permission": permission,
                        "github.has_permission": has_permission,
                    },
                )
                set_span_ok(span)
    
                return has_permission
    
            except Exception as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:1174: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 162 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 22, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1262,7 +1262,7 @@
                         )
                         continue
 
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
....................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x760f91e839d0>

    def test_get_user_team_slugs_success(self):
        """Test successful team membership check."""
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Mock user
            mock_user = Mock()
            mock_user.login = "alice"
    
            # Mock team that has alice as member
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
    
            # Mock org
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(
                123456, "acme", "alice", ["release-team", "deploy-team"]
            )
    
>           assert result == ["release-team", "deploy-team"]
E           AssertionError: assert [] == ['release-tea...'deploy-team']
E             
E             Right contains 2 more items, first extra item: 'release-team'
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E             + []
E             - [
E             -     'release-team',
E             -     'deploy-team',
E             - ]

tests/test_github_client.py:1731: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:22:20.055244Z [warning  ] Failed to check team memberships for alice in acme: (34, 'Numerical result out of range') [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1293 {'extra': {'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "(34, 'Numerical result out of range')"}, 'event': "Failed to check team memberships for alice in acme: (34, 'Numerical result out of range')", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:22:20.055244Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
  
  Right contains 2 more items, first extra item: 'release-team'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     'release-team',
  -     'deploy-team',
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 164 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 23, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1282,7 +1282,7 @@
                 return member_teams
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ** start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________

self = <stampbot.github_client.GitHubAppClient object at 0x7edfbc33c870>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
>               org = client.get_organization(org_name)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1229: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization' id='139499401101392'>
args = ('acme',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: Network error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7edfbe0f7530>

    def test_get_user_team_slugs_general_exception(self):
        """Test team membership check with general exception."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_organization.side_effect = Exception("Network error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1893: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7edfbc33c870>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
                org = client.get_organization(org_name)
                user = client.get_user(username)
    
                for team_ref in allowed_teams:
                    # Extract team slug (handle both "org/team" and "team" formats)
                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
    
                    try:
                        team = org.get_team_by_slug(team_slug)
                        # Check if user is a member
                        if team.has_in_members(user):  # type: ignore[arg-type]
                            member_teams.append(team_slug)
                            logger.debug(
                                "User %s is a member of team %s",
                                username,
                                team_slug,
                                extra={
                                    "org": org_name,
                                    "username": username,
                                    "team": team_slug,
                                },
                            )
                    except GithubException as e:
                        # Team not found or no access - skip silently
                        logger.debug(
                            "Could not check team %s membership: %s",
                            team_slug,
                            e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
                            extra={
                                "org": org_name,
                                "team": team_slug,
                                "username": username,
                            },
                        )
                        continue
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.member_teams": len(member_teams),
                        "github.teams_checked": len(allowed_teams),
                    },
                )
                set_span_ok(span)
    
                return member_teams
    
            except Exception as e:
>               duration = time.time() ** start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               OverflowError: (34, 'Numerical result out of range')

stampbot/github_client.py:1285: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 168 passed, 3 deselected, 1 warning in 1.52s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in self._rate_limit_installations >> live:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x7938c2c5a350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='133284640741360'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:352: in _get_installation_client
    installation_auth = self._installation_auth(installation_id)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:313: in _installation_auth
    self._prune_rate_limit_metrics()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7938bfa39130>

    def _prune_rate_limit_metrics(self) -> None:
        """Drop rate-limit gauge label sets for installations without live credentials.
    
        Prometheus keeps a child series for every label value ever set. Pruning
        whenever new credentials are created bounds the series count by the
        credential cache size, so installation churn cannot grow it without
        limit. Callers hold ``_installation_auths_lock``.
        """
        live = {str(installation_id) for installation_id in self._installation_auths}
>       for stale in self._rate_limit_installations >> live:
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: unsupported operand type(s) for >>: 'set' and 'set'

stampbot/github_client.py:325: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for >>: 'set' and 'set'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 1, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -439,7 +439,7 @@
                     event="APPROVE",
                 )
 
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="success").inc()
 
........................................................................ [ 15%]
.............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________

self = <tests.test_github_client.TestApprovePR object at 0x7f216b252ad0>

    def test_approve_pr_success(self):
        """Test successful PR approval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.approve_pr(123456, "owner/repo", 42)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:631: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:19:01.130631Z [error    ] Failed to approve PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:468 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Failed to approve PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:19:01.130631Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 133 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -459,7 +459,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
 
........................................................................ [ 15%]
..............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________

self = <stampbot.github_client.GitHubAppClient object at 0x7e71c5502cb0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
>               pr.create_review(
                    body=comment,
                    event="APPROVE",
                )

stampbot/github_client.py:437: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().create_review' id='139027104020336'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestApprovePR object at 0x7e71c885ac10>

    def test_approve_pr_failure(self):
        """Test PR approval failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.create_review.side_effect = Exception("API Error")
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.approve_pr(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:668: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e71c5502cb0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
                pr.create_review(
                    body=comment,
                    event="APPROVE",
                )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                github_api_requests_total.labels(operation="approve", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "approved"})
                set_span_ok(span)
    
                logger.info(
                    f"Approved PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:462: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 3, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -508,7 +508,7 @@
                 pr = repo.get_pull(pr_number)
                 head_sha: str = pr.head.sha
 
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="success").inc()
 
........................................................................ [ 15%]
...............................................................F
=================================== FAILURES ===================================
________________ TestGetPRHeadSHA.test_get_pr_head_sha_success _________________

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x7ad834f82d50>

    def test_get_pr_head_sha_success(self):
        """Test successful PR head SHA lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.head.sha = "current-head"
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_pr_head_sha(123456, "owner/repo", 42)
    
>           assert result == "current-head"
E           AssertionError: assert None == 'current-head'

tests/test_github_client.py:713: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:37:32.346617Z [error    ] Failed to get head SHA for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:529 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Failed to get head SHA for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:37:32.346617Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_success - AssertionError: assert None == 'current-head'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 135 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 4, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -520,7 +520,7 @@
                 return head_sha
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
 
........................................................................ [ 15%]
................................................................F
=================================== FAILURES ===================================
_________ TestGetPRHeadSHA.test_get_pr_head_sha_returns_none_on_error __________

self = <stampbot.github_client.GitHubAppClient object at 0x7c7ff67c7890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:507: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='136889034438848'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x7c7ff9cd2e90>

    def test_get_pr_head_sha_returns_none_on_error(self):
        """Test PR head SHA lookup returns None on API errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_pr_head_sha(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:745: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7c7ff67c7890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                head_sha: str = pr.head.sha
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                github_api_requests_total.labels(operation="get_pull", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.head_sha": head_sha})
                set_span_ok(span)
    
                return head_sha
    
            except Exception as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:523: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_returns_none_on_error - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 136 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -599,7 +599,7 @@
                         },
                     )
 
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="success").inc()
 
........................................................................ [ 15%]
.................................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________

self = <tests.test_github_client.TestDismissApproval object at 0x7625b298afd0>

    def test_dismiss_approval_success(self):
        """Test successful approval dismissal."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_review = Mock()
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.dismiss_approval(123456, "owner/repo", 42, 999)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:791: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:37:00.776055Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:37:00.776055Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 137 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 6, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -620,7 +620,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
 
........................................................................ [ 15%]
...................................................................F
=================================== FAILURES ===================================
_ TestDismissApproval.test_dismiss_approval_422_with_review_still_approved_fails _

self = <stampbot.github_client.GitHubAppClient object at 0x7e16608bfb10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
>                   review.dismiss(message)

stampbot/github_client.py:582: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='138634570470736'>
args = ('Duplicate',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 422 {"message": "Validation Failed"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestDismissApproval object at 0x7e1663c49350>

    def test_dismiss_approval_422_with_review_still_approved_fails(self):
        """Test a 422 that leaves the review approved is reported as a failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "APPROVED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Validation Failed"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is False
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:864: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e16608bfb10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
                    review.dismiss(message)
                except GithubException as e:
                    # GitHub answers 422 when a review is already dismissed,
                    # which two replicas cleaning up the same duplicate reach on
                    # purpose. 422 also covers other validation failures, so the
                    # review's state is read back before treating it as done.
                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
                        raise
                    logger.info(
                        "Review %d on PR #%d in %s was already dismissed",
                        review_id,
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "review_id": review_id,
                        },
                    )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                github_api_requests_total.labels(operation="dismiss", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "dismissed"})
                set_span_ok(span)
    
                logger.info(
                    f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                        "review_id": review_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:623: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_422_with_review_still_approved_fails - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 139 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 7, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -682,7 +682,7 @@
 
                 content = _get_repo_contents(repo, file_path, ref)
                 if isinstance(content, list):
-                    duration = time.time() - start_time
+                    duration = time.time() >> start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
                     )
........................................................................ [ 15%]
.......................................................................F [ 30%]

=================================== FAILURES ===================================
________ TestGetRepoFile.test_get_repo_file_returns_none_for_directory _________

self = <tests.test_github_client.TestGetRepoFile object at 0x71f47d47b250>

    def test_get_repo_file_returns_none_for_directory(self):
        """Test that get_repo_file returns None when path is a directory."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Return a list (indicating directory)
            mock_repo = Mock()
            mock_repo.get_contents.return_value = [Mock(), Mock()]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "some/directory")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1021: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x71f479e189b0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'some/directory', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
>                   duration = time.time() >> start_time
                               ^^^^^^^^^^^^^^^^^^^^^^^^^
E                   TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:685: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:16:01.621858Z [warning  ] GitHub could not read some/directory from owner/repo (TypeError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}, 'event': 'GitHub could not read some/directory from owner/repo (TypeError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:16:01.621858Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_for_directory - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 143 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 8, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -691,7 +691,7 @@
                     set_span_ok(span)
                     return None
 
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="success").inc()
 
........................................................................ [ 15%]
......................................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________

self = <tests.test_github_client.TestGetRepoFile object at 0x7ff9ebe73390>

    def test_get_repo_file_success(self):
        """Test successful file retrieval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_content = Mock()
            mock_content.decoded_content = b"file content"
            mock_repo = Mock()
            mock_repo.get_contents.return_value = mock_content
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/repo",
                "stampbot.toml",
                ref="main",
            )

tests/test_github_client.py:978: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7ff9e8b1ff70>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = 'main'
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:694: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:06:32.652757Z [warning  ] GitHub could not read stampbot.toml from owner/repo (TypeError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/repo (TypeError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:06:32.652757Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 9, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -703,7 +703,7 @@
                 return content.decoded_content.decode("utf-8")
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
                 if e.status == 404 and repo is not None:
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <stampbot.github_client.GitHubAppClient object at 0x78e0b3a2c910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='132906482398080'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x78e0bb25d5b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x78e0b3a2c910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:706: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -758,7 +758,7 @@
                 raise
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
                 add_span_attributes(span, {"github.result": "error"})
........................................................................ [ 15%]
........................................................................ [ 30%]
.............F
=================================== FAILURES ===================================
____ TestGetRepoFile.test_get_repo_file_propagates_read_failures[failure2] _____

self = <stampbot.github_client.GitHubAppClient object at 0x77624a942f30>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='131264043763088'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               TimeoutError: GitHub read timed out

/usr/lib/python3.14/unittest/mock.py:1241: TimeoutError

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x77624e031490>
failure = TimeoutError('GitHub read timed out')

    @pytest.mark.parametrize(
        "failure",
        [
            GithubException(403, {"message": "Forbidden"}, None),
            GithubException(500, {"message": "Error"}, None),
            TimeoutError("GitHub read timed out"),
        ],
    )
    def test_get_repo_file_propagates_read_failures(self, failure):
        """Test non-404 policy failures reach the handler's fail-closed path."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = failure
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(type(failure), match=str(failure)):
>               client.get_repo_file(123456, "owner/repo", "stampbot.toml")

tests/test_github_client.py:1410: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x77624a942f30>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                not_found: NotFoundKind = "unconfirmed"
                if e.status == 404 and repo is not None:
                    not_found = _classify_not_found(
                        repo, ref, optional_repo=missing_repository_is_optional
                    )
                repository_is_optionally_missing = not_found == "repository"
                file_is_confirmed_missing = not_found == "file"
                if repository_is_optionally_missing or file_is_confirmed_missing:
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
    
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
    
                    if repository_is_optionally_missing:
                        logger.debug(
                            "Optional policy repository %s was not found",
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    else:
                        logger.debug(
                            "Policy file %s was not found in %s",
                            file_path,
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    return None
    
                github_api_requests_total.labels(operation="get_file", status="error").inc()
                add_span_attributes(span, {"github.result": "error"})
                set_span_error(span, e)
                logger.warning(
                    "GitHub could not read %s from %s (status %s)",
                    file_path,
                    repo_full_name,
                    e.status,
                    extra={
                        "repo": repo_full_name,
                        "file_path": file_path,
                        "installation_id": installation_id,
                    },
                )
                raise
    
            except Exception as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:761: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_read_failures[failure2] - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 157 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 11, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -824,7 +824,7 @@
                             }
                         )
 
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x770edf0f34d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:16:14.585743Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:16:14.585743Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -840,7 +840,7 @@
                 return bot_reviews
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............F
=================================== FAILURES ===================================
_ TestFindBotApprovalReviews.test_find_bot_approval_reviews_returns_none_on_error _

self = <stampbot.github_client.GitHubAppClient object at 0x7d3c9cf3b7f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:809: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='137699312357728'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7d3ca0587610>

    def test_find_bot_approval_reviews_returns_none_on_error(self):
        """Test that a listing failure is reported as None, distinct from no reviews."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1523: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7d3c9cf3b7f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                bot_user = self._bot_login()
    
                bot_reviews: list[BotReview] = []
                for review in pr.get_reviews():
                    if review.user.login == bot_user and review.state in (
                        "APPROVED",
                        "DISMISSED",
                    ):
                        bot_reviews.append(
                            {
                                "id": review.id,
                                "state": review.state,
                                "commit_id": getattr(review, "commit_id", None),
                            }
                        )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                    duration
                )
                github_api_requests_total.labels(operation="find_reviews", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span, {"github.reviews_found": len(bot_reviews), "github.bot_user": bot_user}
                )
                set_span_ok(span)
    
                return bot_reviews
    
            except Exception as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:843: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_returns_none_on_error - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 159 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -897,7 +897,7 @@
 
                 pr.create_review(body=message, event="COMMENT")
 
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x75c83957bed0>

    def test_create_pr_review_comment_success(self):
        """Test successful review comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2098: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:34:53.572462Z [warning  ] Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:928 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:34:53.572462Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 173 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 14, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -919,7 +919,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________

self = <stampbot.github_client.GitHubAppClient object at 0x771ba8201310>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               pr = repo.get_pull(pr_number)
                     ^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:896: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull' id='130960667833216'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x771ba9e34050>

    def test_create_pr_review_comment_failure(self):
        """Test review comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_pull.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )

tests/test_github_client.py:2133: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x771ba8201310>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                pr.create_review(body=message, event="COMMENT")
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                github_api_requests_total.labels(operation="comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted config error review on PR #%d in %s",
                    pr_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:922: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 174 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 15, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -974,7 +974,7 @@
 
                 issue.create_comment(message)
 
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_success ___________

self = <tests.test_github_client.TestCreateIssueComment object at 0x736be4bfc190>

    def test_create_issue_comment_success(self):
        """Test successful issue comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_issue = Mock()
            mock_repo = Mock()
            mock_repo.get_issue.return_value = mock_issue
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2187: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:54:42.018030Z [warning  ] Failed to post issue comment on #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1009 {'extra': {'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Failed to post issue comment on #42 in owner/repo: unsupported operand type(s) for >>: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:54:42.018030Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 175 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 16, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -998,7 +998,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_failure ___________

self = <stampbot.github_client.GitHubAppClient object at 0x76978fc49a90>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               issue = repo.get_issue(issue_number)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:973: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_issue' id='130393323746512'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreateIssueComment object at 0x7697918982d0>

    def test_create_issue_comment_failure(self):
        """Test issue comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_issue.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )

tests/test_github_client.py:2223: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x76978fc49a90>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                issue = repo.get_issue(issue_number)
    
                issue.create_comment(message)
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                    duration
                )
                github_api_requests_total.labels(operation="issue_comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted issue comment on #%d in %s",
                    issue_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "issue_number": issue_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:1001: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_failure - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 176 passed, 3 deselected, 1 warning in 1.54s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 17, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1052,7 +1052,7 @@
                 # The lazy client defers the request until the label is read.
                 repo.get_label(label_name).complete()
 
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x709d10277c50>

    def test_repo_has_label_true(self):
        """Test repo_has_label returns True when label exists."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.return_value = Mock()
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "autoapprove")
    
>           assert result is True
E           assert None is True

tests/test_github_client.py:1937: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 18, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1063,7 +1063,7 @@
                 return True
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <stampbot.github_client.GitHubAppClient object at 0x77f7abc08cd0>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='131905622998192'>
args = ('missing',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x77f7ad91fd90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "missing")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1975: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x77f7abc08cd0>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:1066: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.52s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 19, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1088,7 +1088,7 @@
                 return None
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________

self = <stampbot.github_client.GitHubAppClient object at 0x7dec22540b90>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='138453140697648'>
args = ('autoapprove',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x7dec241ede00>

    def test_repo_has_label_error(self):
        """Test repo_has_label returns None on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "autoapprove")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:2049: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7dec22540b90>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="failure").inc()
    
                if e.status == 404:
                    add_span_attributes(span, {"github.label_found": False})
                    set_span_ok(span)
                    return False
    
                set_span_error(span, e)
                logger.debug(
                    "Could not verify label %s in %s: %s",
                    label_name,
                    repo_full_name,
                    _sanitize_error(e),
                    extra={
                        "repo": repo_full_name,
                        "label": label_name,
                        "installation_id": installation_id,
                        "error": _sanitize_error(e),
                    },
                )
                return None
    
            except Exception as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:1091: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 172 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 20, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1151,7 +1151,7 @@
 
                 has_permission = permission_index >= required_index
 
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________

self = <tests.test_github_client.TestUserHasPermission object at 0x77f229f47750>

    def test_user_has_permission_true(self):
        """Test required permission satisfied returns True."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.return_value = "write"
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "alice", "write")
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:1566: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:00:56.158725Z [warning  ] Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for >>: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1182 {'extra': {'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for >>: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:00:56.158725Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 160 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 21, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1171,7 +1171,7 @@
                 return has_permission
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________

self = <stampbot.github_client.GitHubAppClient object at 0x70f13df07c50>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
>               permission = repo.get_collaborator_permission(username)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_collaborator_permission' id='124181456792480'>
args = ('carol',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestUserHasPermission object at 0x70f14163d810>

    def test_user_has_permission_error(self):
        """Test permission check returns False on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1637: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x70f13df07c50>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
                permission = repo.get_collaborator_permission(username)
                permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
                try:
                    permission_index = permission_order.index(permission)
                    required_index = permission_order.index(required_permission)
                except ValueError:
                    permission_index = -1
                    required_index = 99
    
                has_permission = permission_index >= required_index
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_permission").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_permission", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.permission": permission,
                        "github.has_permission": has_permission,
                    },
                )
                set_span_ok(span)
    
                return has_permission
    
            except Exception as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:1174: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 162 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 22, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1262,7 +1262,7 @@
                         )
                         continue
 
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
....................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7a748577b9d0>

    def test_get_user_team_slugs_success(self):
        """Test successful team membership check."""
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Mock user
            mock_user = Mock()
            mock_user.login = "alice"
    
            # Mock team that has alice as member
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
    
            # Mock org
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(
                123456, "acme", "alice", ["release-team", "deploy-team"]
            )
    
>           assert result == ["release-team", "deploy-team"]
E           AssertionError: assert [] == ['release-tea...'deploy-team']
E             
E             Right contains 2 more items, first extra item: 'release-team'
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E             + []
E             - [
E             -     'release-team',
E             -     'deploy-team',
E             - ]

tests/test_github_client.py:1731: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:28:05.050922Z [warning  ] Failed to check team memberships for alice in acme: unsupported operand type(s) for >>: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1293 {'extra': {'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Failed to check team memberships for alice in acme: unsupported operand type(s) for >>: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:28:05.050922Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
  
  Right contains 2 more items, first extra item: 'release-team'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     'release-team',
  -     'deploy-team',
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 164 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 23, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1282,7 +1282,7 @@
                 return member_teams
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() >> start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________

self = <stampbot.github_client.GitHubAppClient object at 0x7e0c03a2c910>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
>               org = client.get_organization(org_name)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1229: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization' id='138590066524240'>
args = ('acme',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: Network error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7e0c057f3530>

    def test_get_user_team_slugs_general_exception(self):
        """Test team membership check with general exception."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_organization.side_effect = Exception("Network error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1893: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e0c03a2c910>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
                org = client.get_organization(org_name)
                user = client.get_user(username)
    
                for team_ref in allowed_teams:
                    # Extract team slug (handle both "org/team" and "team" formats)
                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
    
                    try:
                        team = org.get_team_by_slug(team_slug)
                        # Check if user is a member
                        if team.has_in_members(user):  # type: ignore[arg-type]
                            member_teams.append(team_slug)
                            logger.debug(
                                "User %s is a member of team %s",
                                username,
                                team_slug,
                                extra={
                                    "org": org_name,
                                    "username": username,
                                    "team": team_slug,
                                },
                            )
                    except GithubException as e:
                        # Team not found or no access - skip silently
                        logger.debug(
                            "Could not check team %s membership: %s",
                            team_slug,
                            e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
                            extra={
                                "org": org_name,
                                "team": team_slug,
                                "username": username,
                            },
                        )
                        continue
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.member_teams": len(member_teams),
                        "github.teams_checked": len(allowed_teams),
                    },
                )
                set_span_ok(span)
    
                return member_teams
    
            except Exception as e:
>               duration = time.time() >> start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/github_client.py:1285: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 168 passed, 3 deselected, 1 warning in 1.52s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in self._rate_limit_installations << live:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x761eb6296350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='129874219014128'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:352: in _get_installation_client
    installation_auth = self._installation_auth(installation_id)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:313: in _installation_auth
    self._prune_rate_limit_metrics()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x761eb2f65090>

    def _prune_rate_limit_metrics(self) -> None:
        """Drop rate-limit gauge label sets for installations without live credentials.
    
        Prometheus keeps a child series for every label value ever set. Pruning
        whenever new credentials are created bounds the series count by the
        credential cache size, so installation churn cannot grow it without
        limit. Callers hold ``_installation_auths_lock``.
        """
        live = {str(installation_id) for installation_id in self._installation_auths}
>       for stale in self._rate_limit_installations << live:
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: unsupported operand type(s) for <<: 'set' and 'set'

stampbot/github_client.py:325: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - TypeError: unsupported operand type(s) for <<: 'set' and 'set'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 1, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -439,7 +439,7 @@
                     event="APPROVE",
                 )
 
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="success").inc()
 
........................................................................ [ 15%]
.............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________

self = <tests.test_github_client.TestApprovePR object at 0x78a899f8ead0>

    def test_approve_pr_success(self):
        """Test successful PR approval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.approve_pr(123456, "owner/repo", 42)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:631: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:03:47.452431Z [error    ] Failed to approve PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:468 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Failed to approve PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:03:47.452431Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 133 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -459,7 +459,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
 
........................................................................ [ 15%]
..............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________

self = <stampbot.github_client.GitHubAppClient object at 0x798c45a42b70>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
>               pr.create_review(
                    body=comment,
                    event="APPROVE",
                )

stampbot/github_client.py:437: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().create_review' id='133643366692720'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestApprovePR object at 0x798c48d42c10>

    def test_approve_pr_failure(self):
        """Test PR approval failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.create_review.side_effect = Exception("API Error")
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.approve_pr(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:668: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x798c45a42b70>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
                pr.create_review(
                    body=comment,
                    event="APPROVE",
                )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                github_api_requests_total.labels(operation="approve", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "approved"})
                set_span_ok(span)
    
                logger.info(
                    f"Approved PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:462: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 3, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -508,7 +508,7 @@
                 pr = repo.get_pull(pr_number)
                 head_sha: str = pr.head.sha
 
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="success").inc()
 
........................................................................ [ 15%]
...............................................................F
=================================== FAILURES ===================================
________________ TestGetPRHeadSHA.test_get_pr_head_sha_success _________________

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x7da29329ad50>

    def test_get_pr_head_sha_success(self):
        """Test successful PR head SHA lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.head.sha = "current-head"
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_pr_head_sha(123456, "owner/repo", 42)
    
>           assert result == "current-head"
E           AssertionError: assert None == 'current-head'

tests/test_github_client.py:713: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:41:38.442941Z [error    ] Failed to get head SHA for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:529 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Failed to get head SHA for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:41:38.442941Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_success - AssertionError: assert None == 'current-head'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 135 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 4, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -520,7 +520,7 @@
                 return head_sha
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
 
........................................................................ [ 15%]
................................................................F
=================================== FAILURES ===================================
_________ TestGetPRHeadSHA.test_get_pr_head_sha_returns_none_on_error __________

self = <stampbot.github_client.GitHubAppClient object at 0x764d1f0d7890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:507: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='130073602341056'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x764d2237ee90>

    def test_get_pr_head_sha_returns_none_on_error(self):
        """Test PR head SHA lookup returns None on API errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_pr_head_sha(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:745: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x764d1f0d7890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                head_sha: str = pr.head.sha
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                github_api_requests_total.labels(operation="get_pull", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.head_sha": head_sha})
                set_span_ok(span)
    
                return head_sha
    
            except Exception as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:523: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_returns_none_on_error - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 136 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -599,7 +599,7 @@
                         },
                     )
 
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="success").inc()
 
........................................................................ [ 15%]
.................................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________

self = <tests.test_github_client.TestDismissApproval object at 0x71631d8eefd0>

    def test_dismiss_approval_success(self):
        """Test successful approval dismissal."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_review = Mock()
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.dismiss_approval(123456, "owner/repo", 42, 999)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:791: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:52:23.500999Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:52:23.500999Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 137 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 6, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -620,7 +620,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
 
........................................................................ [ 15%]
...................................................................F
=================================== FAILURES ===================================
_ TestDismissApproval.test_dismiss_approval_422_with_review_still_approved_fails _

self = <stampbot.github_client.GitHubAppClient object at 0x7682229cfbb0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
>                   review.dismiss(message)

stampbot/github_client.py:582: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='130301294941520'>
args = ('Duplicate',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 422 {"message": "Validation Failed"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestDismissApproval object at 0x768225d35350>

    def test_dismiss_approval_422_with_review_still_approved_fails(self):
        """Test a 422 that leaves the review approved is reported as a failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "APPROVED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Validation Failed"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is False
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:864: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7682229cfbb0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
                    review.dismiss(message)
                except GithubException as e:
                    # GitHub answers 422 when a review is already dismissed,
                    # which two replicas cleaning up the same duplicate reach on
                    # purpose. 422 also covers other validation failures, so the
                    # review's state is read back before treating it as done.
                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
                        raise
                    logger.info(
                        "Review %d on PR #%d in %s was already dismissed",
                        review_id,
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "review_id": review_id,
                        },
                    )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                github_api_requests_total.labels(operation="dismiss", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "dismissed"})
                set_span_ok(span)
    
                logger.info(
                    f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                        "review_id": review_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:623: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_422_with_review_still_approved_fails - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 139 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 7, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -682,7 +682,7 @@
 
                 content = _get_repo_contents(repo, file_path, ref)
                 if isinstance(content, list):
-                    duration = time.time() - start_time
+                    duration = time.time() << start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
                     )
........................................................................ [ 15%]
.......................................................................F [ 30%]

=================================== FAILURES ===================================
________ TestGetRepoFile.test_get_repo_file_returns_none_for_directory _________

self = <tests.test_github_client.TestGetRepoFile object at 0x719a2c71f250>

    def test_get_repo_file_returns_none_for_directory(self):
        """Test that get_repo_file returns None when path is a directory."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Return a list (indicating directory)
            mock_repo = Mock()
            mock_repo.get_contents.return_value = [Mock(), Mock()]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "some/directory")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1021: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x719a28118910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'some/directory', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
>                   duration = time.time() << start_time
                               ^^^^^^^^^^^^^^^^^^^^^^^^^
E                   TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:685: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:35:48.666794Z [warning  ] GitHub could not read some/directory from owner/repo (TypeError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}, 'event': 'GitHub could not read some/directory from owner/repo (TypeError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:35:48.666794Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_for_directory - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 143 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 8, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -691,7 +691,7 @@
                     set_span_ok(span)
                     return None
 
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="success").inc()
 
........................................................................ [ 15%]
......................................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________

self = <tests.test_github_client.TestGetRepoFile object at 0x784a31f17390>

    def test_get_repo_file_success(self):
        """Test successful file retrieval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_content = Mock()
            mock_content.decoded_content = b"file content"
            mock_repo = Mock()
            mock_repo.get_contents.return_value = mock_content
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/repo",
                "stampbot.toml",
                ref="main",
            )

tests/test_github_client.py:978: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x784a2e94c0f0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = 'main'
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:694: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:10:35.720284Z [warning  ] GitHub could not read stampbot.toml from owner/repo (TypeError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/repo (TypeError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:10:35.720284Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 9, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -703,7 +703,7 @@
                 return content.decoded_content.decode("utf-8")
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
                 if e.status == 404 and repo is not None:
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <stampbot.github_client.GitHubAppClient object at 0x70e96a320910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='124147813168000'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x70e96da4d5b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x70e96a320910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:706: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -758,7 +758,7 @@
                 raise
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
                 add_span_attributes(span, {"github.result": "error"})
........................................................................ [ 15%]
........................................................................ [ 30%]
.............F
=================================== FAILURES ===================================
____ TestGetRepoFile.test_get_repo_file_propagates_read_failures[failure2] _____

self = <stampbot.github_client.GitHubAppClient object at 0x7c24766cef30>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='136496073409936'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               TimeoutError: GitHub read timed out

/usr/lib/python3.14/unittest/mock.py:1241: TimeoutError

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x7c2479bfd490>
failure = TimeoutError('GitHub read timed out')

    @pytest.mark.parametrize(
        "failure",
        [
            GithubException(403, {"message": "Forbidden"}, None),
            GithubException(500, {"message": "Error"}, None),
            TimeoutError("GitHub read timed out"),
        ],
    )
    def test_get_repo_file_propagates_read_failures(self, failure):
        """Test non-404 policy failures reach the handler's fail-closed path."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = failure
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(type(failure), match=str(failure)):
>               client.get_repo_file(123456, "owner/repo", "stampbot.toml")

tests/test_github_client.py:1410: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7c24766cef30>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                not_found: NotFoundKind = "unconfirmed"
                if e.status == 404 and repo is not None:
                    not_found = _classify_not_found(
                        repo, ref, optional_repo=missing_repository_is_optional
                    )
                repository_is_optionally_missing = not_found == "repository"
                file_is_confirmed_missing = not_found == "file"
                if repository_is_optionally_missing or file_is_confirmed_missing:
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
    
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
    
                    if repository_is_optionally_missing:
                        logger.debug(
                            "Optional policy repository %s was not found",
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    else:
                        logger.debug(
                            "Policy file %s was not found in %s",
                            file_path,
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    return None
    
                github_api_requests_total.labels(operation="get_file", status="error").inc()
                add_span_attributes(span, {"github.result": "error"})
                set_span_error(span, e)
                logger.warning(
                    "GitHub could not read %s from %s (status %s)",
                    file_path,
                    repo_full_name,
                    e.status,
                    extra={
                        "repo": repo_full_name,
                        "file_path": file_path,
                        "installation_id": installation_id,
                    },
                )
                raise
    
            except Exception as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:761: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_read_failures[failure2] - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 157 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 11, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -824,7 +824,7 @@
                             }
                         )
 
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7886e30f74d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:04:43.390325Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:04:43.390325Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -840,7 +840,7 @@
                 return bot_reviews
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............F
=================================== FAILURES ===================================
_ TestFindBotApprovalReviews.test_find_bot_approval_reviews_returns_none_on_error _

self = <stampbot.github_client.GitHubAppClient object at 0x7e3490a5b890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:809: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='138764257857888'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7e349409f610>

    def test_find_bot_approval_reviews_returns_none_on_error(self):
        """Test that a listing failure is reported as None, distinct from no reviews."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1523: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e3490a5b890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                bot_user = self._bot_login()
    
                bot_reviews: list[BotReview] = []
                for review in pr.get_reviews():
                    if review.user.login == bot_user and review.state in (
                        "APPROVED",
                        "DISMISSED",
                    ):
                        bot_reviews.append(
                            {
                                "id": review.id,
                                "state": review.state,
                                "commit_id": getattr(review, "commit_id", None),
                            }
                        )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                    duration
                )
                github_api_requests_total.labels(operation="find_reviews", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span, {"github.reviews_found": len(bot_reviews), "github.bot_user": bot_user}
                )
                set_span_ok(span)
    
                return bot_reviews
    
            except Exception as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:843: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_returns_none_on_error - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 159 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -897,7 +897,7 @@
 
                 pr.create_review(body=message, event="COMMENT")
 
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x748430783ed0>

    def test_create_pr_review_comment_success(self):
        """Test successful review comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2098: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:21:06.053835Z [warning  ] Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:928 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:21:06.053835Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 173 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 14, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -919,7 +919,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________

self = <stampbot.github_client.GitHubAppClient object at 0x7bfe32819310>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               pr = repo.get_pull(pr_number)
                     ^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:896: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull' id='136331698615168'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7bfe344a0050>

    def test_create_pr_review_comment_failure(self):
        """Test review comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_pull.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )

tests/test_github_client.py:2133: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7bfe32819310>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                pr.create_review(body=message, event="COMMENT")
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                github_api_requests_total.labels(operation="comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted config error review on PR #%d in %s",
                    pr_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:922: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 174 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 15, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -974,7 +974,7 @@
 
                 issue.create_comment(message)
 
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_success ___________

self = <tests.test_github_client.TestCreateIssueComment object at 0x7fa908aa0190>

    def test_create_issue_comment_success(self):
        """Test successful issue comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_issue = Mock()
            mock_repo = Mock()
            mock_repo.get_issue.return_value = mock_issue
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2187: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:20:14.899253Z [warning  ] Failed to post issue comment on #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1009 {'extra': {'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Failed to post issue comment on #42 in owner/repo: unsupported operand type(s) for <<: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:20:14.899253Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 175 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 16, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -998,7 +998,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_failure ___________

self = <stampbot.github_client.GitHubAppClient object at 0x7d2a1c22d9f0>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               issue = repo.get_issue(issue_number)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:973: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_issue' id='137619813725392'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreateIssueComment object at 0x7d2a1dea42d0>

    def test_create_issue_comment_failure(self):
        """Test issue comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_issue.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )

tests/test_github_client.py:2223: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7d2a1c22d9f0>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                issue = repo.get_issue(issue_number)
    
                issue.create_comment(message)
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                    duration
                )
                github_api_requests_total.labels(operation="issue_comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted issue comment on #%d in %s",
                    issue_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "issue_number": issue_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:1001: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_failure - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 176 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 17, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1052,7 +1052,7 @@
                 # The lazy client defers the request until the label is read.
                 repo.get_label(label_name).complete()
 
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x7406e679bc50>

    def test_repo_has_label_true(self):
        """Test repo_has_label returns True when label exists."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.return_value = Mock()
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "autoapprove")
    
>           assert result is True
E           assert None is True

tests/test_github_client.py:1937: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected, 1 warning in 1.52s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 18, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1063,7 +1063,7 @@
                 return True
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <stampbot.github_client.GitHubAppClient object at 0x7a4418718cd0>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='134432887307440'>
args = ('missing',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x7a441a49fd90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "missing")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1975: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7a4418718cd0>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:1066: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 19, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1088,7 +1088,7 @@
                 return None
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________

self = <stampbot.github_client.GitHubAppClient object at 0x727db8a04c30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='125884293050928'>
args = ('autoapprove',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x727dba849e00>

    def test_repo_has_label_error(self):
        """Test repo_has_label returns None on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "autoapprove")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:2049: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x727db8a04c30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="failure").inc()
    
                if e.status == 404:
                    add_span_attributes(span, {"github.label_found": False})
                    set_span_ok(span)
                    return False
    
                set_span_error(span, e)
                logger.debug(
                    "Could not verify label %s in %s: %s",
                    label_name,
                    repo_full_name,
                    _sanitize_error(e),
                    extra={
                        "repo": repo_full_name,
                        "label": label_name,
                        "installation_id": installation_id,
                        "error": _sanitize_error(e),
                    },
                )
                return None
    
            except Exception as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:1091: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 172 passed, 3 deselected, 1 warning in 1.51s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 20, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1151,7 +1151,7 @@
 
                 has_permission = permission_index >= required_index
 
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________

self = <tests.test_github_client.TestUserHasPermission object at 0x79c248f2b750>

    def test_user_has_permission_true(self):
        """Test required permission satisfied returns True."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.return_value = "write"
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "alice", "write")
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:1566: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:30:38.707762Z [warning  ] Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for <<: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1182 {'extra': {'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for <<: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:30:38.707762Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 160 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 21, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1171,7 +1171,7 @@
                 return has_permission
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________

self = <stampbot.github_client.GitHubAppClient object at 0x7e15cda33c50>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
>               permission = repo.get_collaborator_permission(username)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_collaborator_permission' id='138632135568288'>
args = ('carol',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestUserHasPermission object at 0x7e15d105d810>

    def test_user_has_permission_error(self):
        """Test permission check returns False on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1637: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e15cda33c50>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
                permission = repo.get_collaborator_permission(username)
                permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
                try:
                    permission_index = permission_order.index(permission)
                    required_index = permission_order.index(required_permission)
                except ValueError:
                    permission_index = -1
                    required_index = 99
    
                has_permission = permission_index >= required_index
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_permission").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_permission", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.permission": permission,
                        "github.has_permission": has_permission,
                    },
                )
                set_span_ok(span)
    
                return has_permission
    
            except Exception as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:1174: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 162 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 22, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1262,7 +1262,7 @@
                         )
                         continue
 
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
....................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x72abe779f9d0>

    def test_get_user_team_slugs_success(self):
        """Test successful team membership check."""
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Mock user
            mock_user = Mock()
            mock_user.login = "alice"
    
            # Mock team that has alice as member
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
    
            # Mock org
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(
                123456, "acme", "alice", ["release-team", "deploy-team"]
            )
    
>           assert result == ["release-team", "deploy-team"]
E           AssertionError: assert [] == ['release-tea...'deploy-team']
E             
E             Right contains 2 more items, first extra item: 'release-team'
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E             + []
E             - [
E             -     'release-team',
E             -     'deploy-team',
E             - ]

tests/test_github_client.py:1731: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:11:46.637320Z [warning  ] Failed to check team memberships for alice in acme: unsupported operand type(s) for <<: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1293 {'extra': {'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Failed to check team memberships for alice in acme: unsupported operand type(s) for <<: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:11:46.637320Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
  
  Right contains 2 more items, first extra item: 'release-team'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     'release-team',
  -     'deploy-team',
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 164 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 23, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1282,7 +1282,7 @@
                 return member_teams
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() << start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________

self = <stampbot.github_client.GitHubAppClient object at 0x78913e9f8910>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
>               org = client.get_organization(org_name)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1229: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization' id='132565214986320'>
args = ('acme',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: Network error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x78914056f530>

    def test_get_user_team_slugs_general_exception(self):
        """Test team membership check with general exception."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_organization.side_effect = Exception("Network error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1893: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x78913e9f8910>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
                org = client.get_organization(org_name)
                user = client.get_user(username)
    
                for team_ref in allowed_teams:
                    # Extract team slug (handle both "org/team" and "team" formats)
                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
    
                    try:
                        team = org.get_team_by_slug(team_slug)
                        # Check if user is a member
                        if team.has_in_members(user):  # type: ignore[arg-type]
                            member_teams.append(team_slug)
                            logger.debug(
                                "User %s is a member of team %s",
                                username,
                                team_slug,
                                extra={
                                    "org": org_name,
                                    "username": username,
                                    "team": team_slug,
                                },
                            )
                    except GithubException as e:
                        # Team not found or no access - skip silently
                        logger.debug(
                            "Could not check team %s membership: %s",
                            team_slug,
                            e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
                            extra={
                                "org": org_name,
                                "team": team_slug,
                                "username": username,
                            },
                        )
                        continue
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.member_teams": len(member_teams),
                        "github.teams_checked": len(allowed_teams),
                    },
                )
                set_span_ok(span)
    
                return member_teams
    
            except Exception as e:
>               duration = time.time() << start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/github_client.py:1285: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 168 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in self._rate_limit_installations | live:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
........................................................F
=================================== FAILURES ===================================
_ TestRateLimitLabelPruning.test_prune_removes_labels_for_evicted_installations _

self = <tests.test_github_client.TestRateLimitLabelPruning object at 0x7665ba47e5d0>

    def test_prune_removes_labels_for_evicted_installations(self):
        """Test gauge label sets follow the credential cache, so churn cannot grow them."""
        from stampbot.github_client import GitHubAppClient
        from stampbot.metrics import github_api_rate_limit_limit, github_api_rate_limit_remaining
    
        client = GitHubAppClient()
        for installation_id in (9001, 9002):
            fake = Mock()
            fake.requester.rate_limiting = (10, 5000)
            client._update_rate_limit_metrics(fake, installation_id)
        assert client._rate_limit_installations == {"9001", "9002"}
    
        # Only 9002 still has live credentials.
        client._installation_auths[9002] = Mock()
        with client._installation_auths_lock:
            client._prune_rate_limit_metrics()
    
        assert client._rate_limit_installations == {"9002"}
        for gauge in (github_api_rate_limit_remaining, github_api_rate_limit_limit):
            labelled = {sample.labels["installation_id"] for sample in gauge.collect()[0].samples}
            assert "9001" not in labelled
>           assert "9002" in labelled
E           AssertionError: assert '9002' in set()

tests/test_github_client.py:530: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRateLimitLabelPruning::test_prune_removes_labels_for_evicted_installations - AssertionError: assert '9002' in set()
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 128 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 1, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -439,7 +439,7 @@
                     event="APPROVE",
                 )
 
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="success").inc()
 
........................................................................ [ 15%]
.............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________

self = <tests.test_github_client.TestApprovePR object at 0x74535b17ead0>

    def test_approve_pr_success(self):
        """Test successful PR approval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.approve_pr(123456, "owner/repo", 42)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:631: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:36:55.232684Z [error    ] Failed to approve PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:468 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Failed to approve PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:36:55.232684Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 133 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -459,7 +459,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
 
........................................................................ [ 15%]
..............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________

self = <stampbot.github_client.GitHubAppClient object at 0x78b8ecf32b70>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
>               pr.create_review(
                    body=comment,
                    event="APPROVE",
                )

stampbot/github_client.py:437: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().create_review' id='132735640352624'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestApprovePR object at 0x78b8f0286c10>

    def test_approve_pr_failure(self):
        """Test PR approval failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.create_review.side_effect = Exception("API Error")
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.approve_pr(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:668: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x78b8ecf32b70>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
                pr.create_review(
                    body=comment,
                    event="APPROVE",
                )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                github_api_requests_total.labels(operation="approve", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "approved"})
                set_span_ok(span)
    
                logger.info(
                    f"Approved PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:462: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 3, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -508,7 +508,7 @@
                 pr = repo.get_pull(pr_number)
                 head_sha: str = pr.head.sha
 
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="success").inc()
 
........................................................................ [ 15%]
...............................................................F
=================================== FAILURES ===================================
________________ TestGetPRHeadSHA.test_get_pr_head_sha_success _________________

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x7bd77866ad50>

    def test_get_pr_head_sha_success(self):
        """Test successful PR head SHA lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.head.sha = "current-head"
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_pr_head_sha(123456, "owner/repo", 42)
    
>           assert result == "current-head"
E           AssertionError: assert None == 'current-head'

tests/test_github_client.py:713: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:28:34.103230Z [error    ] Failed to get head SHA for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:529 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Failed to get head SHA for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:28:34.103230Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_success - AssertionError: assert None == 'current-head'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 135 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 4, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -520,7 +520,7 @@
                 return head_sha
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
 
........................................................................ [ 15%]
................................................................F
=================================== FAILURES ===================================
_________ TestGetPRHeadSHA.test_get_pr_head_sha_returns_none_on_error __________

self = <stampbot.github_client.GitHubAppClient object at 0x72c42dc4f7f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:507: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='126186903959744'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x72c430f7ee90>

    def test_get_pr_head_sha_returns_none_on_error(self):
        """Test PR head SHA lookup returns None on API errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_pr_head_sha(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:745: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x72c42dc4f7f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                head_sha: str = pr.head.sha
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                github_api_requests_total.labels(operation="get_pull", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.head_sha": head_sha})
                set_span_ok(span)
    
                return head_sha
    
            except Exception as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:523: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_returns_none_on_error - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 136 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -599,7 +599,7 @@
                         },
                     )
 
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="success").inc()
 
........................................................................ [ 15%]
.................................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________

self = <tests.test_github_client.TestDismissApproval object at 0x70ae53c82fd0>

    def test_dismiss_approval_success(self):
        """Test successful approval dismissal."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_review = Mock()
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.dismiss_approval(123456, "owner/repo", 42, 999)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:791: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:05:29.432285Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:05:29.432285Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 137 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 6, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -620,7 +620,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
 
........................................................................ [ 15%]
...................................................................F
=================================== FAILURES ===================================
_ TestDismissApproval.test_dismiss_approval_422_with_review_still_approved_fails _

self = <stampbot.github_client.GitHubAppClient object at 0x7a3f6641fb10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
>                   review.dismiss(message)

stampbot/github_client.py:582: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='134412714022224'>
args = ('Duplicate',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 422 {"message": "Validation Failed"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestDismissApproval object at 0x7a3f69849350>

    def test_dismiss_approval_422_with_review_still_approved_fails(self):
        """Test a 422 that leaves the review approved is reported as a failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "APPROVED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Validation Failed"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is False
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:864: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7a3f6641fb10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
                    review.dismiss(message)
                except GithubException as e:
                    # GitHub answers 422 when a review is already dismissed,
                    # which two replicas cleaning up the same duplicate reach on
                    # purpose. 422 also covers other validation failures, so the
                    # review's state is read back before treating it as done.
                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
                        raise
                    logger.info(
                        "Review %d on PR #%d in %s was already dismissed",
                        review_id,
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "review_id": review_id,
                        },
                    )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                github_api_requests_total.labels(operation="dismiss", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "dismissed"})
                set_span_ok(span)
    
                logger.info(
                    f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                        "review_id": review_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:623: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_422_with_review_still_approved_fails - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 139 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 7, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -682,7 +682,7 @@
 
                 content = _get_repo_contents(repo, file_path, ref)
                 if isinstance(content, list):
-                    duration = time.time() - start_time
+                    duration = time.time() | start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
                     )
........................................................................ [ 15%]
.......................................................................F [ 30%]

=================================== FAILURES ===================================
________ TestGetRepoFile.test_get_repo_file_returns_none_for_directory _________

self = <tests.test_github_client.TestGetRepoFile object at 0x71562f79f250>

    def test_get_repo_file_returns_none_for_directory(self):
        """Test that get_repo_file returns None when path is a directory."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Return a list (indicating directory)
            mock_repo = Mock()
            mock_repo.get_contents.return_value = [Mock(), Mock()]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "some/directory")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1021: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x715627f249b0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'some/directory', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
>                   duration = time.time() | start_time
                               ^^^^^^^^^^^^^^^^^^^^^^^^
E                   TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:685: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:25:34.726471Z [warning  ] GitHub could not read some/directory from owner/repo (TypeError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}, 'event': 'GitHub could not read some/directory from owner/repo (TypeError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:25:34.726471Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_for_directory - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 143 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 8, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -691,7 +691,7 @@
                     set_span_ok(span)
                     return None
 
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="success").inc()
 
........................................................................ [ 15%]
......................................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________

self = <tests.test_github_client.TestGetRepoFile object at 0x758706a8f390>

    def test_get_repo_file_success(self):
        """Test successful file retrieval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_content = Mock()
            mock_content.decoded_content = b"file content"
            mock_repo = Mock()
            mock_repo.get_contents.return_value = mock_content
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/repo",
                "stampbot.toml",
                ref="main",
            )

tests/test_github_client.py:978: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x758703300190>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = 'main'
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:694: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:24:24.639284Z [warning  ] GitHub could not read stampbot.toml from owner/repo (TypeError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/repo (TypeError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:24:24.639284Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 9, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -703,7 +703,7 @@
                 return content.decoded_content.decode("utf-8")
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
                 if e.status == 404 and repo is not None:
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <stampbot.github_client.GitHubAppClient object at 0x7ba23ad64910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='135936700773248'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x7ba23e3315b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7ba23ad64910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:706: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -758,7 +758,7 @@
                 raise
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
                 add_span_attributes(span, {"github.result": "error"})
........................................................................ [ 15%]
........................................................................ [ 30%]
.............F
=================================== FAILURES ===================================
____ TestGetRepoFile.test_get_repo_file_propagates_read_failures[failure2] _____

self = <stampbot.github_client.GitHubAppClient object at 0x7da8f302af30>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='138164612580752'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               TimeoutError: GitHub read timed out

/usr/lib/python3.14/unittest/mock.py:1241: TimeoutError

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x7da8f670d490>
failure = TimeoutError('GitHub read timed out')

    @pytest.mark.parametrize(
        "failure",
        [
            GithubException(403, {"message": "Forbidden"}, None),
            GithubException(500, {"message": "Error"}, None),
            TimeoutError("GitHub read timed out"),
        ],
    )
    def test_get_repo_file_propagates_read_failures(self, failure):
        """Test non-404 policy failures reach the handler's fail-closed path."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = failure
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(type(failure), match=str(failure)):
>               client.get_repo_file(123456, "owner/repo", "stampbot.toml")

tests/test_github_client.py:1410: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7da8f302af30>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                not_found: NotFoundKind = "unconfirmed"
                if e.status == 404 and repo is not None:
                    not_found = _classify_not_found(
                        repo, ref, optional_repo=missing_repository_is_optional
                    )
                repository_is_optionally_missing = not_found == "repository"
                file_is_confirmed_missing = not_found == "file"
                if repository_is_optionally_missing or file_is_confirmed_missing:
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
    
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
    
                    if repository_is_optionally_missing:
                        logger.debug(
                            "Optional policy repository %s was not found",
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    else:
                        logger.debug(
                            "Policy file %s was not found in %s",
                            file_path,
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    return None
    
                github_api_requests_total.labels(operation="get_file", status="error").inc()
                add_span_attributes(span, {"github.result": "error"})
                set_span_error(span, e)
                logger.warning(
                    "GitHub could not read %s from %s (status %s)",
                    file_path,
                    repo_full_name,
                    e.status,
                    extra={
                        "repo": repo_full_name,
                        "file_path": file_path,
                        "installation_id": installation_id,
                    },
                )
                raise
    
            except Exception as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:761: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_read_failures[failure2] - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 157 passed, 3 deselected, 1 warning in 1.51s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 11, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -824,7 +824,7 @@
                             }
                         )
 
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x751dfeb9f4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:53:47.243606Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:53:47.243606Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -840,7 +840,7 @@
                 return bot_reviews
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............F
=================================== FAILURES ===================================
_ TestFindBotApprovalReviews.test_find_bot_approval_reviews_returns_none_on_error _

self = <stampbot.github_client.GitHubAppClient object at 0x7f431675b890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:809: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='139926113702240'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7f4319d9f610>

    def test_find_bot_approval_reviews_returns_none_on_error(self):
        """Test that a listing failure is reported as None, distinct from no reviews."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1523: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7f431675b890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                bot_user = self._bot_login()
    
                bot_reviews: list[BotReview] = []
                for review in pr.get_reviews():
                    if review.user.login == bot_user and review.state in (
                        "APPROVED",
                        "DISMISSED",
                    ):
                        bot_reviews.append(
                            {
                                "id": review.id,
                                "state": review.state,
                                "commit_id": getattr(review, "commit_id", None),
                            }
                        )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                    duration
                )
                github_api_requests_total.labels(operation="find_reviews", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span, {"github.reviews_found": len(bot_reviews), "github.bot_user": bot_user}
                )
                set_span_ok(span)
    
                return bot_reviews
    
            except Exception as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:843: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_returns_none_on_error - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 159 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -897,7 +897,7 @@
 
                 pr.create_review(body=message, event="COMMENT")
 
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7ca4280ffed0>

    def test_create_pr_review_comment_success(self):
        """Test successful review comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2098: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:49:39.253316Z [warning  ] Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:928 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:49:39.253316Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 173 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 14, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -919,7 +919,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________

self = <stampbot.github_client.GitHubAppClient object at 0x7a2a58d0d310>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               pr = repo.get_pull(pr_number)
                     ^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:896: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull' id='134322296648576'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7a2a5a9a4050>

    def test_create_pr_review_comment_failure(self):
        """Test review comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_pull.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )

tests/test_github_client.py:2133: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7a2a58d0d310>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                pr.create_review(body=message, event="COMMENT")
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                github_api_requests_total.labels(operation="comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted config error review on PR #%d in %s",
                    pr_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:922: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 174 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 15, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -974,7 +974,7 @@
 
                 issue.create_comment(message)
 
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_success ___________

self = <tests.test_github_client.TestCreateIssueComment object at 0x711b920a8190>

    def test_create_issue_comment_success(self):
        """Test successful issue comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_issue = Mock()
            mock_repo = Mock()
            mock_repo.get_issue.return_value = mock_issue
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2187: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:35:39.087556Z [warning  ] Failed to post issue comment on #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1009 {'extra': {'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Failed to post issue comment on #42 in owner/repo: unsupported operand type(s) for |: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:35:39.087556Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 175 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 16, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -998,7 +998,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_failure ___________

self = <stampbot.github_client.GitHubAppClient object at 0x7401620119f0>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               issue = repo.get_issue(issue_number)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:973: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_issue' id='127549287593168'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreateIssueComment object at 0x740163dfc2d0>

    def test_create_issue_comment_failure(self):
        """Test issue comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_issue.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )

tests/test_github_client.py:2223: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7401620119f0>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                issue = repo.get_issue(issue_number)
    
                issue.create_comment(message)
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                    duration
                )
                github_api_requests_total.labels(operation="issue_comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted issue comment on #%d in %s",
                    issue_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "issue_number": issue_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:1001: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_failure - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 176 passed, 3 deselected, 1 warning in 1.54s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 17, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1052,7 +1052,7 @@
                 # The lazy client defers the request until the label is read.
                 repo.get_label(label_name).complete()
 
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x78562ee2fc50>

    def test_repo_has_label_true(self):
        """Test repo_has_label returns True when label exists."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.return_value = Mock()
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "autoapprove")
    
>           assert result is True
E           assert None is True

tests/test_github_client.py:1937: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 18, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1063,7 +1063,7 @@
                 return True
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <stampbot.github_client.GitHubAppClient object at 0x7648a170cc30>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='130054319045808'>
args = ('missing',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x7648a36ebd90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "missing")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1975: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7648a170cc30>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:1066: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 19, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1088,7 +1088,7 @@
                 return None
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________

self = <stampbot.github_client.GitHubAppClient object at 0x7a82b2014c30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='134701749826096'>
args = ('autoapprove',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x7a82b3e4de00>

    def test_repo_has_label_error(self):
        """Test repo_has_label returns None on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "autoapprove")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:2049: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7a82b2014c30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="failure").inc()
    
                if e.status == 404:
                    add_span_attributes(span, {"github.label_found": False})
                    set_span_ok(span)
                    return False
    
                set_span_error(span, e)
                logger.debug(
                    "Could not verify label %s in %s: %s",
                    label_name,
                    repo_full_name,
                    _sanitize_error(e),
                    extra={
                        "repo": repo_full_name,
                        "label": label_name,
                        "installation_id": installation_id,
                        "error": _sanitize_error(e),
                    },
                )
                return None
    
            except Exception as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:1091: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 172 passed, 3 deselected, 1 warning in 1.52s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 20, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1151,7 +1151,7 @@
 
                 has_permission = permission_index >= required_index
 
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________

self = <tests.test_github_client.TestUserHasPermission object at 0x745fdde83750>

    def test_user_has_permission_true(self):
        """Test required permission satisfied returns True."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.return_value = "write"
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "alice", "write")
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:1566: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:37:35.846317Z [warning  ] Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for |: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1182 {'extra': {'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for |: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:37:35.846317Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 160 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 21, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1171,7 +1171,7 @@
                 return has_permission
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________

self = <stampbot.github_client.GitHubAppClient object at 0x7e7cb1a57bb0>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
>               permission = repo.get_collaborator_permission(username)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_collaborator_permission' id='139074048207776'>
args = ('carol',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestUserHasPermission object at 0x7e7cb4fc5810>

    def test_user_has_permission_error(self):
        """Test permission check returns False on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1637: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e7cb1a57bb0>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
                permission = repo.get_collaborator_permission(username)
                permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
                try:
                    permission_index = permission_order.index(permission)
                    required_index = permission_order.index(required_permission)
                except ValueError:
                    permission_index = -1
                    required_index = 99
    
                has_permission = permission_index >= required_index
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_permission").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_permission", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.permission": permission,
                        "github.has_permission": has_permission,
                    },
                )
                set_span_ok(span)
    
                return has_permission
    
            except Exception as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:1174: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 162 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 22, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1262,7 +1262,7 @@
                         )
                         continue
 
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
....................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x708b956839d0>

    def test_get_user_team_slugs_success(self):
        """Test successful team membership check."""
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Mock user
            mock_user = Mock()
            mock_user.login = "alice"
    
            # Mock team that has alice as member
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
    
            # Mock org
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(
                123456, "acme", "alice", ["release-team", "deploy-team"]
            )
    
>           assert result == ["release-team", "deploy-team"]
E           AssertionError: assert [] == ['release-tea...'deploy-team']
E             
E             Right contains 2 more items, first extra item: 'release-team'
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E             + []
E             - [
E             -     'release-team',
E             -     'deploy-team',
E             - ]

tests/test_github_client.py:1731: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:27:54.688086Z [warning  ] Failed to check team memberships for alice in acme: unsupported operand type(s) for |: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1293 {'extra': {'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Failed to check team memberships for alice in acme: unsupported operand type(s) for |: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:27:54.688086Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
  
  Right contains 2 more items, first extra item: 'release-team'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     'release-team',
  -     'deploy-team',
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 164 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 23, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1282,7 +1282,7 @@
                 return member_teams
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() | start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________

self = <stampbot.github_client.GitHubAppClient object at 0x7ff3453cc910>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
>               org = client.get_organization(org_name)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1229: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization' id='140682814144592'>
args = ('acme',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: Network error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7ff346fef530>

    def test_get_user_team_slugs_general_exception(self):
        """Test team membership check with general exception."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_organization.side_effect = Exception("Network error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1893: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7ff3453cc910>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
                org = client.get_organization(org_name)
                user = client.get_user(username)
    
                for team_ref in allowed_teams:
                    # Extract team slug (handle both "org/team" and "team" formats)
                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
    
                    try:
                        team = org.get_team_by_slug(team_slug)
                        # Check if user is a member
                        if team.has_in_members(user):  # type: ignore[arg-type]
                            member_teams.append(team_slug)
                            logger.debug(
                                "User %s is a member of team %s",
                                username,
                                team_slug,
                                extra={
                                    "org": org_name,
                                    "username": username,
                                    "team": team_slug,
                                },
                            )
                    except GithubException as e:
                        # Team not found or no access - skip silently
                        logger.debug(
                            "Could not check team %s membership: %s",
                            team_slug,
                            e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
                            extra={
                                "org": org_name,
                                "team": team_slug,
                                "username": username,
                            },
                        )
                        continue
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.member_teams": len(member_teams),
                        "github.teams_checked": len(allowed_teams),
                    },
                )
                set_span_ok(span)
    
                return member_teams
    
            except Exception as e:
>               duration = time.time() | start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/github_client.py:1285: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 168 passed, 3 deselected, 1 warning in 1.54s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in self._rate_limit_installations & live:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
........................................................F
=================================== FAILURES ===================================
_ TestRateLimitLabelPruning.test_prune_removes_labels_for_evicted_installations _

self = <tests.test_github_client.TestRateLimitLabelPruning object at 0x75e2e7c8a5d0>

    def test_prune_removes_labels_for_evicted_installations(self):
        """Test gauge label sets follow the credential cache, so churn cannot grow them."""
        from stampbot.github_client import GitHubAppClient
        from stampbot.metrics import github_api_rate_limit_limit, github_api_rate_limit_remaining
    
        client = GitHubAppClient()
        for installation_id in (9001, 9002):
            fake = Mock()
            fake.requester.rate_limiting = (10, 5000)
            client._update_rate_limit_metrics(fake, installation_id)
        assert client._rate_limit_installations == {"9001", "9002"}
    
        # Only 9002 still has live credentials.
        client._installation_auths[9002] = Mock()
        with client._installation_auths_lock:
            client._prune_rate_limit_metrics()
    
        assert client._rate_limit_installations == {"9002"}
        for gauge in (github_api_rate_limit_remaining, github_api_rate_limit_limit):
            labelled = {sample.labels["installation_id"] for sample in gauge.collect()[0].samples}
>           assert "9001" not in labelled
E           AssertionError: assert '9001' not in {'9001'}

tests/test_github_client.py:529: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRateLimitLabelPruning::test_prune_removes_labels_for_evicted_installations - AssertionError: assert '9001' not in {'9001'}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 128 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 1, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -439,7 +439,7 @@
                     event="APPROVE",
                 )
 
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="success").inc()
 
........................................................................ [ 15%]
.............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________

self = <tests.test_github_client.TestApprovePR object at 0x73c99770ead0>

    def test_approve_pr_success(self):
        """Test successful PR approval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.approve_pr(123456, "owner/repo", 42)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:631: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:27:20.495050Z [error    ] Failed to approve PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:468 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Failed to approve PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:27:20.495050Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 133 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -459,7 +459,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
 
........................................................................ [ 15%]
..............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________

self = <stampbot.github_client.GitHubAppClient object at 0x7492ef732c10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
>               pr.create_review(
                    body=comment,
                    event="APPROVE",
                )

stampbot/github_client.py:437: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().create_review' id='128174429026160'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestApprovePR object at 0x7492f29b6c10>

    def test_approve_pr_failure(self):
        """Test PR approval failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.create_review.side_effect = Exception("API Error")
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.approve_pr(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:668: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7492ef732c10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
                pr.create_review(
                    body=comment,
                    event="APPROVE",
                )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                github_api_requests_total.labels(operation="approve", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "approved"})
                set_span_ok(span)
    
                logger.info(
                    f"Approved PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:462: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 3, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -508,7 +508,7 @@
                 pr = repo.get_pull(pr_number)
                 head_sha: str = pr.head.sha
 
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="success").inc()
 
........................................................................ [ 15%]
...............................................................F
=================================== FAILURES ===================================
________________ TestGetPRHeadSHA.test_get_pr_head_sha_success _________________

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x75ff16e76d50>

    def test_get_pr_head_sha_success(self):
        """Test successful PR head SHA lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.head.sha = "current-head"
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_pr_head_sha(123456, "owner/repo", 42)
    
>           assert result == "current-head"
E           AssertionError: assert None == 'current-head'

tests/test_github_client.py:713: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:08:14.684255Z [error    ] Failed to get head SHA for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:529 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Failed to get head SHA for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:08:14.684255Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_success - AssertionError: assert None == 'current-head'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 135 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 4, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -520,7 +520,7 @@
                 return head_sha
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
 
........................................................................ [ 15%]
................................................................F
=================================== FAILURES ===================================
_________ TestGetPRHeadSHA.test_get_pr_head_sha_returns_none_on_error __________

self = <stampbot.github_client.GitHubAppClient object at 0x74264733f890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:507: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='127707748890816'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x74264a68ee90>

    def test_get_pr_head_sha_returns_none_on_error(self):
        """Test PR head SHA lookup returns None on API errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_pr_head_sha(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:745: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x74264733f890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                head_sha: str = pr.head.sha
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                github_api_requests_total.labels(operation="get_pull", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.head_sha": head_sha})
                set_span_ok(span)
    
                return head_sha
    
            except Exception as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:523: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_returns_none_on_error - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 136 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -599,7 +599,7 @@
                         },
                     )
 
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="success").inc()
 
........................................................................ [ 15%]
.................................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________

self = <tests.test_github_client.TestDismissApproval object at 0x7aa369a6efd0>

    def test_dismiss_approval_success(self):
        """Test successful approval dismissal."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_review = Mock()
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.dismiss_approval(123456, "owner/repo", 42, 999)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:791: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:26:43.662847Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:26:43.662847Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 137 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 6, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -620,7 +620,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
 
........................................................................ [ 15%]
...................................................................F
=================================== FAILURES ===================================
_ TestDismissApproval.test_dismiss_approval_422_with_review_still_approved_fails _

self = <stampbot.github_client.GitHubAppClient object at 0x77be1a4cfbb0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
>                   review.dismiss(message)

stampbot/github_client.py:582: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='131658365982032'>
args = ('Duplicate',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 422 {"message": "Validation Failed"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestDismissApproval object at 0x77be1d855350>

    def test_dismiss_approval_422_with_review_still_approved_fails(self):
        """Test a 422 that leaves the review approved is reported as a failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "APPROVED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Validation Failed"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is False
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:864: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x77be1a4cfbb0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
                    review.dismiss(message)
                except GithubException as e:
                    # GitHub answers 422 when a review is already dismissed,
                    # which two replicas cleaning up the same duplicate reach on
                    # purpose. 422 also covers other validation failures, so the
                    # review's state is read back before treating it as done.
                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
                        raise
                    logger.info(
                        "Review %d on PR #%d in %s was already dismissed",
                        review_id,
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "review_id": review_id,
                        },
                    )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                github_api_requests_total.labels(operation="dismiss", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "dismissed"})
                set_span_ok(span)
    
                logger.info(
                    f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                        "review_id": review_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:623: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_422_with_review_still_approved_fails - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 139 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 7, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -682,7 +682,7 @@
 
                 content = _get_repo_contents(repo, file_path, ref)
                 if isinstance(content, list):
-                    duration = time.time() - start_time
+                    duration = time.time() & start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
                     )
........................................................................ [ 15%]
.......................................................................F [ 30%]

=================================== FAILURES ===================================
________ TestGetRepoFile.test_get_repo_file_returns_none_for_directory _________

self = <tests.test_github_client.TestGetRepoFile object at 0x7f2831473250>

    def test_get_repo_file_returns_none_for_directory(self):
        """Test that get_repo_file returns None when path is a directory."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Return a list (indicating directory)
            mock_repo = Mock()
            mock_repo.get_contents.return_value = [Mock(), Mock()]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "some/directory")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1021: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7f282de00910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'some/directory', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
>                   duration = time.time() & start_time
                               ^^^^^^^^^^^^^^^^^^^^^^^^
E                   TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:685: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:14:07.907443Z [warning  ] GitHub could not read some/directory from owner/repo (TypeError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}, 'event': 'GitHub could not read some/directory from owner/repo (TypeError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:14:07.907443Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_for_directory - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 143 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 8, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -691,7 +691,7 @@
                     set_span_ok(span)
                     return None
 
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="success").inc()
 
........................................................................ [ 15%]
......................................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________

self = <tests.test_github_client.TestGetRepoFile object at 0x79aa39ffb390>

    def test_get_repo_file_success(self):
        """Test successful file retrieval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_content = Mock()
            mock_content.decoded_content = b"file content"
            mock_repo = Mock()
            mock_repo.get_contents.return_value = mock_content
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/repo",
                "stampbot.toml",
                ref="main",
            )

tests/test_github_client.py:978: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x79aa36b27f70>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = 'main'
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:694: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:32:24.083139Z [warning  ] GitHub could not read stampbot.toml from owner/repo (TypeError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/repo (TypeError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:32:24.083139Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 9, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -703,7 +703,7 @@
                 return content.decoded_content.decode("utf-8")
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
                 if e.status == 404 and repo is not None:
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <stampbot.github_client.GitHubAppClient object at 0x7877eb20c910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='132456441705344'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x7877ee7f15b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7877eb20c910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:706: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -758,7 +758,7 @@
                 raise
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
                 add_span_attributes(span, {"github.result": "error"})
........................................................................ [ 15%]
........................................................................ [ 30%]
.............F
=================================== FAILURES ===================================
____ TestGetRepoFile.test_get_repo_file_propagates_read_failures[failure2] _____

self = <stampbot.github_client.GitHubAppClient object at 0x7be306b9ee90>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='136215027533200'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               TimeoutError: GitHub read timed out

/usr/lib/python3.14/unittest/mock.py:1241: TimeoutError

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x7be30a175490>
failure = TimeoutError('GitHub read timed out')

    @pytest.mark.parametrize(
        "failure",
        [
            GithubException(403, {"message": "Forbidden"}, None),
            GithubException(500, {"message": "Error"}, None),
            TimeoutError("GitHub read timed out"),
        ],
    )
    def test_get_repo_file_propagates_read_failures(self, failure):
        """Test non-404 policy failures reach the handler's fail-closed path."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = failure
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(type(failure), match=str(failure)):
>               client.get_repo_file(123456, "owner/repo", "stampbot.toml")

tests/test_github_client.py:1410: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7be306b9ee90>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                not_found: NotFoundKind = "unconfirmed"
                if e.status == 404 and repo is not None:
                    not_found = _classify_not_found(
                        repo, ref, optional_repo=missing_repository_is_optional
                    )
                repository_is_optionally_missing = not_found == "repository"
                file_is_confirmed_missing = not_found == "file"
                if repository_is_optionally_missing or file_is_confirmed_missing:
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
    
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
    
                    if repository_is_optionally_missing:
                        logger.debug(
                            "Optional policy repository %s was not found",
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    else:
                        logger.debug(
                            "Policy file %s was not found in %s",
                            file_path,
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    return None
    
                github_api_requests_total.labels(operation="get_file", status="error").inc()
                add_span_attributes(span, {"github.result": "error"})
                set_span_error(span, e)
                logger.warning(
                    "GitHub could not read %s from %s (status %s)",
                    file_path,
                    repo_full_name,
                    e.status,
                    extra={
                        "repo": repo_full_name,
                        "file_path": file_path,
                        "installation_id": installation_id,
                    },
                )
                raise
    
            except Exception as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:761: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_read_failures[failure2] - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 157 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 11, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -824,7 +824,7 @@
                             }
                         )
 
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x71963be334d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:40:54.073785Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:40:54.073785Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.50s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -840,7 +840,7 @@
                 return bot_reviews
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............F
=================================== FAILURES ===================================
_ TestFindBotApprovalReviews.test_find_bot_approval_reviews_returns_none_on_error _

self = <stampbot.github_client.GitHubAppClient object at 0x7737ddf037f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:809: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='131081859436896'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7737e1673610>

    def test_find_bot_approval_reviews_returns_none_on_error(self):
        """Test that a listing failure is reported as None, distinct from no reviews."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1523: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7737ddf037f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                bot_user = self._bot_login()
    
                bot_reviews: list[BotReview] = []
                for review in pr.get_reviews():
                    if review.user.login == bot_user and review.state in (
                        "APPROVED",
                        "DISMISSED",
                    ):
                        bot_reviews.append(
                            {
                                "id": review.id,
                                "state": review.state,
                                "commit_id": getattr(review, "commit_id", None),
                            }
                        )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                    duration
                )
                github_api_requests_total.labels(operation="find_reviews", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span, {"github.reviews_found": len(bot_reviews), "github.bot_user": bot_user}
                )
                set_span_ok(span)
    
                return bot_reviews
    
            except Exception as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:843: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_returns_none_on_error - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 159 passed, 3 deselected, 1 warning in 1.50s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -897,7 +897,7 @@
 
                 pr.create_review(body=message, event="COMMENT")
 
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7801eada7ed0>

    def test_create_pr_review_comment_success(self):
        """Test successful review comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2098: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:16:07.875502Z [warning  ] Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:928 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:16:07.875502Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 173 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 14, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -919,7 +919,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________

self = <stampbot.github_client.GitHubAppClient object at 0x73ecef221310>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               pr = repo.get_pull(pr_number)
                     ^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:896: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull' id='127461460816768'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x73ecf0ea4050>

    def test_create_pr_review_comment_failure(self):
        """Test review comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_pull.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )

tests/test_github_client.py:2133: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x73ecef221310>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                pr.create_review(body=message, event="COMMENT")
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                github_api_requests_total.labels(operation="comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted config error review on PR #%d in %s",
                    pr_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:922: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 174 passed, 3 deselected, 1 warning in 1.54s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 15, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -974,7 +974,7 @@
 
                 issue.create_comment(message)
 
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_success ___________

self = <tests.test_github_client.TestCreateIssueComment object at 0x719db3ed0190>

    def test_create_issue_comment_success(self):
        """Test successful issue comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_issue = Mock()
            mock_repo = Mock()
            mock_repo.get_issue.return_value = mock_issue
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2187: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:30:55.792426Z [warning  ] Failed to post issue comment on #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1009 {'extra': {'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Failed to post issue comment on #42 in owner/repo: unsupported operand type(s) for &: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:30:55.792426Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 175 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 16, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -998,7 +998,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_failure ___________

self = <stampbot.github_client.GitHubAppClient object at 0x74d336551a90>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               issue = repo.get_issue(issue_number)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:973: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_issue' id='128450498098384'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreateIssueComment object at 0x74d3380a82d0>

    def test_create_issue_comment_failure(self):
        """Test issue comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_issue.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )

tests/test_github_client.py:2223: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x74d336551a90>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                issue = repo.get_issue(issue_number)
    
                issue.create_comment(message)
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                    duration
                )
                github_api_requests_total.labels(operation="issue_comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted issue comment on #%d in %s",
                    issue_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "issue_number": issue_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:1001: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_failure - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 176 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 17, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1052,7 +1052,7 @@
                 # The lazy client defers the request until the label is read.
                 repo.get_label(label_name).complete()
 
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x7c8e20653c50>

    def test_repo_has_label_true(self):
        """Test repo_has_label returns True when label exists."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.return_value = Mock()
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "autoapprove")
    
>           assert result is True
E           assert None is True

tests/test_github_client.py:1937: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 18, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1063,7 +1063,7 @@
                 return True
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <stampbot.github_client.GitHubAppClient object at 0x76810ce20c30>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='130296639798448'>
args = ('missing',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x76810eb2bd90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "missing")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1975: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x76810ce20c30>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:1066: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.51s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 19, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1088,7 +1088,7 @@
                 return None
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________

self = <stampbot.github_client.GitHubAppClient object at 0x7cf1be224c30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='137377717924400'>
args = ('autoapprove',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x7cf1c004de00>

    def test_repo_has_label_error(self):
        """Test repo_has_label returns None on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "autoapprove")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:2049: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7cf1be224c30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="failure").inc()
    
                if e.status == 404:
                    add_span_attributes(span, {"github.label_found": False})
                    set_span_ok(span)
                    return False
    
                set_span_error(span, e)
                logger.debug(
                    "Could not verify label %s in %s: %s",
                    label_name,
                    repo_full_name,
                    _sanitize_error(e),
                    extra={
                        "repo": repo_full_name,
                        "label": label_name,
                        "installation_id": installation_id,
                        "error": _sanitize_error(e),
                    },
                )
                return None
    
            except Exception as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:1091: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 172 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 20, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1151,7 +1151,7 @@
 
                 has_permission = permission_index >= required_index
 
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________

self = <tests.test_github_client.TestUserHasPermission object at 0x716308173750>

    def test_user_has_permission_true(self):
        """Test required permission satisfied returns True."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.return_value = "write"
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "alice", "write")
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:1566: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:06:30.134948Z [warning  ] Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for &: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1182 {'extra': {'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for &: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:06:30.134948Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 160 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 21, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1171,7 +1171,7 @@
                 return has_permission
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________

self = <stampbot.github_client.GitHubAppClient object at 0x7c880e837c50>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
>               permission = repo.get_collaborator_permission(username)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_collaborator_permission' id='136923828890528'>
args = ('carol',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestUserHasPermission object at 0x7c8811f21810>

    def test_user_has_permission_error(self):
        """Test permission check returns False on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1637: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7c880e837c50>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
                permission = repo.get_collaborator_permission(username)
                permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
                try:
                    permission_index = permission_order.index(permission)
                    required_index = permission_order.index(required_permission)
                except ValueError:
                    permission_index = -1
                    required_index = 99
    
                has_permission = permission_index >= required_index
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_permission").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_permission", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.permission": permission,
                        "github.has_permission": has_permission,
                    },
                )
                set_span_ok(span)
    
                return has_permission
    
            except Exception as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:1174: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 162 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 22, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1262,7 +1262,7 @@
                         )
                         continue
 
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
....................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x76c99bb239d0>

    def test_get_user_team_slugs_success(self):
        """Test successful team membership check."""
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Mock user
            mock_user = Mock()
            mock_user.login = "alice"
    
            # Mock team that has alice as member
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
    
            # Mock org
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(
                123456, "acme", "alice", ["release-team", "deploy-team"]
            )
    
>           assert result == ["release-team", "deploy-team"]
E           AssertionError: assert [] == ['release-tea...'deploy-team']
E             
E             Right contains 2 more items, first extra item: 'release-team'
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E             + []
E             - [
E             -     'release-team',
E             -     'deploy-team',
E             - ]

tests/test_github_client.py:1731: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:27:59.866027Z [warning  ] Failed to check team memberships for alice in acme: unsupported operand type(s) for &: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1293 {'extra': {'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Failed to check team memberships for alice in acme: unsupported operand type(s) for &: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:27:59.866027Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
  
  Right contains 2 more items, first extra item: 'release-team'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     'release-team',
  -     'deploy-team',
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 164 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 23, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1282,7 +1282,7 @@
                 return member_teams
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() & start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________

self = <stampbot.github_client.GitHubAppClient object at 0x7f4e9023c870>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
>               org = client.get_organization(org_name)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1229: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization' id='139975401160784'>
args = ('acme',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: Network error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7f4e91fdf530>

    def test_get_user_team_slugs_general_exception(self):
        """Test team membership check with general exception."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_organization.side_effect = Exception("Network error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1893: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7f4e9023c870>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
                org = client.get_organization(org_name)
                user = client.get_user(username)
    
                for team_ref in allowed_teams:
                    # Extract team slug (handle both "org/team" and "team" formats)
                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
    
                    try:
                        team = org.get_team_by_slug(team_slug)
                        # Check if user is a member
                        if team.has_in_members(user):  # type: ignore[arg-type]
                            member_teams.append(team_slug)
                            logger.debug(
                                "User %s is a member of team %s",
                                username,
                                team_slug,
                                extra={
                                    "org": org_name,
                                    "username": username,
                                    "team": team_slug,
                                },
                            )
                    except GithubException as e:
                        # Team not found or no access - skip silently
                        logger.debug(
                            "Could not check team %s membership: %s",
                            team_slug,
                            e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
                            extra={
                                "org": org_name,
                                "team": team_slug,
                                "username": username,
                            },
                        )
                        continue
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.member_teams": len(member_teams),
                        "github.teams_checked": len(allowed_teams),
                    },
                )
                set_span_ok(span)
    
                return member_teams
    
            except Exception as e:
>               duration = time.time() & start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/github_client.py:1285: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 168 passed, 3 deselected, 1 warning in 1.51s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in self._rate_limit_installations ^ live:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 1, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -439,7 +439,7 @@
                     event="APPROVE",
                 )
 
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="success").inc()
 
........................................................................ [ 15%]
.............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________

self = <tests.test_github_client.TestApprovePR object at 0x758fcf77ead0>

    def test_approve_pr_success(self):
        """Test successful PR approval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.approve_pr(123456, "owner/repo", 42)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:631: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:35:23.011764Z [error    ] Failed to approve PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:468 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Failed to approve PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:35:23.011764Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 133 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -459,7 +459,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
 
........................................................................ [ 15%]
..............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________

self = <stampbot.github_client.GitHubAppClient object at 0x76a85f51ac10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
>               pr.create_review(
                    body=comment,
                    event="APPROVE",
                )

stampbot/github_client.py:437: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().create_review' id='130465520678768'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestApprovePR object at 0x76a8628fac10>

    def test_approve_pr_failure(self):
        """Test PR approval failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.create_review.side_effect = Exception("API Error")
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.approve_pr(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:668: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x76a85f51ac10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
                pr.create_review(
                    body=comment,
                    event="APPROVE",
                )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                github_api_requests_total.labels(operation="approve", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "approved"})
                set_span_ok(span)
    
                logger.info(
                    f"Approved PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:462: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 3, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -508,7 +508,7 @@
                 pr = repo.get_pull(pr_number)
                 head_sha: str = pr.head.sha
 
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="success").inc()
 
........................................................................ [ 15%]
...............................................................F
=================================== FAILURES ===================================
________________ TestGetPRHeadSHA.test_get_pr_head_sha_success _________________

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x772a2377ad50>

    def test_get_pr_head_sha_success(self):
        """Test successful PR head SHA lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.head.sha = "current-head"
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_pr_head_sha(123456, "owner/repo", 42)
    
>           assert result == "current-head"
E           AssertionError: assert None == 'current-head'

tests/test_github_client.py:713: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:33:22.869250Z [error    ] Failed to get head SHA for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:529 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Failed to get head SHA for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:33:22.869250Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_success - AssertionError: assert None == 'current-head'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 135 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 4, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -520,7 +520,7 @@
                 return head_sha
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
 
........................................................................ [ 15%]
................................................................F
=================================== FAILURES ===================================
_________ TestGetPRHeadSHA.test_get_pr_head_sha_returns_none_on_error __________

self = <stampbot.github_client.GitHubAppClient object at 0x7e667ea677f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:507: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='138978674571456'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x7e6681d8ae90>

    def test_get_pr_head_sha_returns_none_on_error(self):
        """Test PR head SHA lookup returns None on API errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_pr_head_sha(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:745: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e667ea677f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                head_sha: str = pr.head.sha
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                github_api_requests_total.labels(operation="get_pull", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.head_sha": head_sha})
                set_span_ok(span)
    
                return head_sha
    
            except Exception as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:523: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_returns_none_on_error - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 136 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -599,7 +599,7 @@
                         },
                     )
 
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="success").inc()
 
........................................................................ [ 15%]
.................................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________

self = <tests.test_github_client.TestDismissApproval object at 0x7bc1d889afd0>

    def test_dismiss_approval_success(self):
        """Test successful approval dismissal."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_review = Mock()
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.dismiss_approval(123456, "owner/repo", 42, 999)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:791: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:31:49.270569Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Failed to dismiss approval for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:31:49.270569Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 137 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 6, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -620,7 +620,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
 
........................................................................ [ 15%]
...................................................................F
=================================== FAILURES ===================================
_ TestDismissApproval.test_dismiss_approval_422_with_review_still_approved_fails _

self = <stampbot.github_client.GitHubAppClient object at 0x7b77f28cfbb0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
>                   review.dismiss(message)

stampbot/github_client.py:582: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='135755096986960'>
args = ('Duplicate',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 422 {"message": "Validation Failed"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestDismissApproval object at 0x7b77f5c45350>

    def test_dismiss_approval_422_with_review_still_approved_fails(self):
        """Test a 422 that leaves the review approved is reported as a failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "APPROVED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Validation Failed"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is False
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:864: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7b77f28cfbb0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
                    review.dismiss(message)
                except GithubException as e:
                    # GitHub answers 422 when a review is already dismissed,
                    # which two replicas cleaning up the same duplicate reach on
                    # purpose. 422 also covers other validation failures, so the
                    # review's state is read back before treating it as done.
                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
                        raise
                    logger.info(
                        "Review %d on PR #%d in %s was already dismissed",
                        review_id,
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "review_id": review_id,
                        },
                    )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                github_api_requests_total.labels(operation="dismiss", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "dismissed"})
                set_span_ok(span)
    
                logger.info(
                    f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                        "review_id": review_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:623: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_422_with_review_still_approved_fails - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 139 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 7, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -682,7 +682,7 @@
 
                 content = _get_repo_contents(repo, file_path, ref)
                 if isinstance(content, list):
-                    duration = time.time() - start_time
+                    duration = time.time() ^ start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
                     )
........................................................................ [ 15%]
.......................................................................F [ 30%]

=================================== FAILURES ===================================
________ TestGetRepoFile.test_get_repo_file_returns_none_for_directory _________

self = <tests.test_github_client.TestGetRepoFile object at 0x74856692b250>

    def test_get_repo_file_returns_none_for_directory(self):
        """Test that get_repo_file returns None when path is a directory."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Return a list (indicating directory)
            mock_repo = Mock()
            mock_repo.get_contents.return_value = [Mock(), Mock()]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "some/directory")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1021: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7485633449b0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'some/directory', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
>                   duration = time.time() ^ start_time
                               ^^^^^^^^^^^^^^^^^^^^^^^^
E                   TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:685: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:00:32.694154Z [warning  ] GitHub could not read some/directory from owner/repo (TypeError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'some/directory', 'installation_id': 123456}, 'event': 'GitHub could not read some/directory from owner/repo (TypeError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:00:32.694154Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_for_directory - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 143 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 8, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -691,7 +691,7 @@
                     set_span_ok(span)
                     return None
 
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="success").inc()
 
........................................................................ [ 15%]
......................................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________

self = <tests.test_github_client.TestGetRepoFile object at 0x73da53d9b390>

    def test_get_repo_file_success(self):
        """Test successful file retrieval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_content = Mock()
            mock_content.decoded_content = b"file content"
            mock_repo = Mock()
            mock_repo.get_contents.return_value = mock_content
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/repo",
                "stampbot.toml",
                ref="main",
            )

tests/test_github_client.py:978: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x73da5072c0f0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = 'main'
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:694: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:21:19.422900Z [warning  ] GitHub could not read stampbot.toml from owner/repo (TypeError) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:766 {'extra': {'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/repo (TypeError)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:21:19.422900Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 9, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -703,7 +703,7 @@
                 return content.decoded_content.decode("utf-8")
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
                 if e.status == 404 and repo is not None:
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <stampbot.github_client.GitHubAppClient object at 0x7bdac6e1c910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='136179571684224'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x7bdaca5455b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7bdac6e1c910>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:706: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -758,7 +758,7 @@
                 raise
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
                 add_span_attributes(span, {"github.result": "error"})
........................................................................ [ 15%]
........................................................................ [ 30%]
.............F
=================================== FAILURES ===================================
____ TestGetRepoFile.test_get_repo_file_propagates_read_failures[failure2] _____

self = <stampbot.github_client.GitHubAppClient object at 0x755a52332f30>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='129030814123408'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               TimeoutError: GitHub read timed out

/usr/lib/python3.14/unittest/mock.py:1241: TimeoutError

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x755a55a05490>
failure = TimeoutError('GitHub read timed out')

    @pytest.mark.parametrize(
        "failure",
        [
            GithubException(403, {"message": "Forbidden"}, None),
            GithubException(500, {"message": "Error"}, None),
            TimeoutError("GitHub read timed out"),
        ],
    )
    def test_get_repo_file_propagates_read_failures(self, failure):
        """Test non-404 policy failures reach the handler's fail-closed path."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = failure
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(type(failure), match=str(failure)):
>               client.get_repo_file(123456, "owner/repo", "stampbot.toml")

tests/test_github_client.py:1410: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x755a52332f30>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                not_found: NotFoundKind = "unconfirmed"
                if e.status == 404 and repo is not None:
                    not_found = _classify_not_found(
                        repo, ref, optional_repo=missing_repository_is_optional
                    )
                repository_is_optionally_missing = not_found == "repository"
                file_is_confirmed_missing = not_found == "file"
                if repository_is_optionally_missing or file_is_confirmed_missing:
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
    
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
    
                    if repository_is_optionally_missing:
                        logger.debug(
                            "Optional policy repository %s was not found",
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    else:
                        logger.debug(
                            "Policy file %s was not found in %s",
                            file_path,
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    return None
    
                github_api_requests_total.labels(operation="get_file", status="error").inc()
                add_span_attributes(span, {"github.result": "error"})
                set_span_error(span, e)
                logger.warning(
                    "GitHub could not read %s from %s (status %s)",
                    file_path,
                    repo_full_name,
                    e.status,
                    extra={
                        "repo": repo_full_name,
                        "file_path": file_path,
                        "installation_id": installation_id,
                    },
                )
                raise
    
            except Exception as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:761: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_read_failures[failure2] - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 157 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 11, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -824,7 +824,7 @@
                             }
                         )
 
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7931a0b8f4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:28:32.596181Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:28:32.596181Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -840,7 +840,7 @@
                 return bot_reviews
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............F
=================================== FAILURES ===================================
_ TestFindBotApprovalReviews.test_find_bot_approval_reviews_returns_none_on_error _

self = <stampbot.github_client.GitHubAppClient object at 0x7e5039367890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:809: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='138883049771360'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7e503c907610>

    def test_find_bot_approval_reviews_returns_none_on_error(self):
        """Test that a listing failure is reported as None, distinct from no reviews."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1523: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e5039367890>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                bot_user = self._bot_login()
    
                bot_reviews: list[BotReview] = []
                for review in pr.get_reviews():
                    if review.user.login == bot_user and review.state in (
                        "APPROVED",
                        "DISMISSED",
                    ):
                        bot_reviews.append(
                            {
                                "id": review.id,
                                "state": review.state,
                                "commit_id": getattr(review, "commit_id", None),
                            }
                        )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                    duration
                )
                github_api_requests_total.labels(operation="find_reviews", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span, {"github.reviews_found": len(bot_reviews), "github.bot_user": bot_user}
                )
                set_span_ok(span)
    
                return bot_reviews
    
            except Exception as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:843: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_returns_none_on_error - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 159 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -897,7 +897,7 @@
 
                 pr.create_review(body=message, event="COMMENT")
 
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7eba8687fed0>

    def test_create_pr_review_comment_success(self):
        """Test successful review comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2098: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:05:32.031821Z [warning  ] Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:928 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Failed to post config error review for PR #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:05:32.031821Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 173 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 14, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -919,7 +919,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________

self = <stampbot.github_client.GitHubAppClient object at 0x7ec32dbfd310>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               pr = repo.get_pull(pr_number)
                     ^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:896: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull' id='139376748524416'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7ec32f790050>

    def test_create_pr_review_comment_failure(self):
        """Test review comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_pull.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )

tests/test_github_client.py:2133: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7ec32dbfd310>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                pr.create_review(body=message, event="COMMENT")
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                github_api_requests_total.labels(operation="comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted config error review on PR #%d in %s",
                    pr_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:922: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 174 passed, 3 deselected, 1 warning in 1.56s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 15, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -974,7 +974,7 @@
 
                 issue.create_comment(message)
 
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
...............................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_success ___________

self = <tests.test_github_client.TestCreateIssueComment object at 0x7634800a0190>

    def test_create_issue_comment_success(self):
        """Test successful issue comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_issue = Mock()
            mock_repo = Mock()
            mock_repo.get_issue.return_value = mock_issue
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2187: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:20:45.091933Z [warning  ] Failed to post issue comment on #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1009 {'extra': {'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Failed to post issue comment on #42 in owner/repo: unsupported operand type(s) for ^: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:20:45.091933Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 175 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 16, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -998,7 +998,7 @@
                 return True
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_failure ___________

self = <stampbot.github_client.GitHubAppClient object at 0x78618ee29a90>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               issue = repo.get_issue(issue_number)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:973: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_issue' id='132360403989712'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreateIssueComment object at 0x786190aa42d0>

    def test_create_issue_comment_failure(self):
        """Test issue comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_issue.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )

tests/test_github_client.py:2223: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x78618ee29a90>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                issue = repo.get_issue(issue_number)
    
                issue.create_comment(message)
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                    duration
                )
                github_api_requests_total.labels(operation="issue_comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted issue comment on #%d in %s",
                    issue_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "issue_number": issue_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
            except Exception as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:1001: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_failure - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 176 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 17, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1052,7 +1052,7 @@
                 # The lazy client defers the request until the label is read.
                 repo.get_label(label_name).complete()
 
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="success").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x7f5d2baf3c50>

    def test_repo_has_label_true(self):
        """Test repo_has_label returns True when label exists."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.return_value = Mock()
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "autoapprove")
    
>           assert result is True
E           assert None is True

tests/test_github_client.py:1937: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert None is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 18, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1063,7 +1063,7 @@
                 return True
 
             except GithubException as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <stampbot.github_client.GitHubAppClient object at 0x77780c624cd0>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='131357488413872'>
args = ('missing',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x77780e30fd90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "missing")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1975: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x77780c624cd0>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:1066: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 19, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1088,7 +1088,7 @@
                 return None
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________

self = <stampbot.github_client.GitHubAppClient object at 0x74b7d8410c30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='128332955038256'>
args = ('autoapprove',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x74b7da24de00>

    def test_repo_has_label_error(self):
        """Test repo_has_label returns None on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "autoapprove")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:2049: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x74b7d8410c30>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="failure").inc()
    
                if e.status == 404:
                    add_span_attributes(span, {"github.label_found": False})
                    set_span_ok(span)
                    return False
    
                set_span_error(span, e)
                logger.debug(
                    "Could not verify label %s in %s: %s",
                    label_name,
                    repo_full_name,
                    _sanitize_error(e),
                    extra={
                        "repo": repo_full_name,
                        "label": label_name,
                        "installation_id": installation_id,
                        "error": _sanitize_error(e),
                    },
                )
                return None
    
            except Exception as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:1091: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 172 passed, 3 deselected, 1 warning in 1.51s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 20, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1151,7 +1151,7 @@
 
                 has_permission = permission_index >= required_index
 
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________

self = <tests.test_github_client.TestUserHasPermission object at 0x71aa3e143750>

    def test_user_has_permission_true(self):
        """Test required permission satisfied returns True."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.return_value = "write"
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "alice", "write")
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:1566: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:39:17.119127Z [warning  ] Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for ^: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1182 {'extra': {'repo': 'owner/repo', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Failed to check collaborator permission for alice in owner/repo: unsupported operand type(s) for ^: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:39:17.119127Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 160 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 21, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1171,7 +1171,7 @@
                 return has_permission
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
..................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________

self = <stampbot.github_client.GitHubAppClient object at 0x73169f633c50>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
>               permission = repo.get_collaborator_permission(username)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_collaborator_permission' id='126541028584352'>
args = ('carol',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestUserHasPermission object at 0x7316a2d45810>

    def test_user_has_permission_error(self):
        """Test permission check returns False on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1637: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x73169f633c50>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
                permission = repo.get_collaborator_permission(username)
                permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
                try:
                    permission_index = permission_order.index(permission)
                    required_index = permission_order.index(required_permission)
                except ValueError:
                    permission_index = -1
                    required_index = 99
    
                has_permission = permission_index >= required_index
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_permission").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_permission", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.permission": permission,
                        "github.has_permission": has_permission,
                    },
                )
                set_span_ok(span)
    
                return has_permission
    
            except Exception as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:1174: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 162 passed, 3 deselected, 1 warning in 1.50s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 22, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1262,7 +1262,7 @@
                         )
                         continue
 
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
....................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x76d2dfc7f9d0>

    def test_get_user_team_slugs_success(self):
        """Test successful team membership check."""
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Mock user
            mock_user = Mock()
            mock_user.login = "alice"
    
            # Mock team that has alice as member
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
    
            # Mock org
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(
                123456, "acme", "alice", ["release-team", "deploy-team"]
            )
    
>           assert result == ["release-team", "deploy-team"]
E           AssertionError: assert [] == ['release-tea...'deploy-team']
E             
E             Right contains 2 more items, first extra item: 'release-team'
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E             + []
E             - [
E             -     'release-team',
E             -     'deploy-team',
E             - ]

tests/test_github_client.py:1731: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:15:03.372248Z [warning  ] Failed to check team memberships for alice in acme: unsupported operand type(s) for ^: 'float' and 'float' [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1293 {'extra': {'org': 'acme', 'username': 'alice', 'installation_id': 123456, 'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Failed to check team memberships for alice in acme: unsupported operand type(s) for ^: 'float' and 'float'", 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:15:03.372248Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
  
  Right contains 2 more items, first extra item: 'release-team'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     'release-team',
  -     'deploy-team',
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 164 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 23, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1282,7 +1282,7 @@
                 return member_teams
 
             except Exception as e:
-                duration = time.time() - start_time
+                duration = time.time() ^ start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
                 )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________

self = <stampbot.github_client.GitHubAppClient object at 0x79808c424870>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
>               org = client.get_organization(org_name)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1229: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization' id='133593016713296'>
args = ('acme',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: Network error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x79808e1df530>

    def test_get_user_team_slugs_general_exception(self):
        """Test team membership check with general exception."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_organization.side_effect = Exception("Network error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1893: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x79808c424870>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
                org = client.get_organization(org_name)
                user = client.get_user(username)
    
                for team_ref in allowed_teams:
                    # Extract team slug (handle both "org/team" and "team" formats)
                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
    
                    try:
                        team = org.get_team_by_slug(team_slug)
                        # Check if user is a member
                        if team.has_in_members(user):  # type: ignore[arg-type]
                            member_teams.append(team_slug)
                            logger.debug(
                                "User %s is a member of team %s",
                                username,
                                team_slug,
                                extra={
                                    "org": org_name,
                                    "username": username,
                                    "team": team_slug,
                                },
                            )
                    except GithubException as e:
                        # Team not found or no access - skip silently
                        logger.debug(
                            "Could not check team %s membership: %s",
                            team_slug,
                            e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
                            extra={
                                "org": org_name,
                                "team": team_slug,
                                "username": username,
                            },
                        )
                        continue
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.member_teams": len(member_teams),
                        "github.teams_checked": len(allowed_teams),
                    },
                )
                set_span_ok(span)
    
                return member_teams
    
            except Exception as e:
>               duration = time.time() ^ start_time
                           ^^^^^^^^^^^^^^^^^^^^^^^^
E               TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/github_client.py:1285: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 168 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceBinaryOperator_Mul_Add, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 + 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Mul_Add, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -111,7 +111,7 @@
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
 
 
-def _classify_not_found(repo: Repository, ref: str | None, *, optional_repo: bool) -> NotFoundKind:
+def _classify_not_found(repo: Repository, ref: str | None, +, optional_repo: bool) -> NotFoundKind:
     """Decide what a contents 404 means.
 
     An optional fallback repository may itself be missing, which one repository
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 114
E       def _classify_not_found(repo: Repository, ref: str | None, +, optional_repo: bool) -> NotFoundKind:
E                                                                  ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/ReplaceBinaryOperator_Mul_Add, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -646,7 +646,7 @@
         repo_full_name: str,
         file_path: str,
         ref: str | None = None,
-        *,
+        +,
         missing_repository_is_optional: bool = False,
     ) -> str | None:
         """Get file content from repository.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 649
E       +,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/ReplaceBinaryOperator_Mul_Sub, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 - 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7a8930e03ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7a892d5ac9e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
>           raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E           ValueError: Private key exceeds -960 bytes

stampbot/github_client.py:249: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key exceeds -960 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceBinaryOperator_Mul_Sub, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -111,7 +111,7 @@
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
 
 
-def _classify_not_found(repo: Repository, ref: str | None, *, optional_repo: bool) -> NotFoundKind:
+def _classify_not_found(repo: Repository, ref: str | None, -, optional_repo: bool) -> NotFoundKind:
     """Decide what a contents 404 means.
 
     An optional fallback repository may itself be missing, which one repository
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 114
E       def _classify_not_found(repo: Repository, ref: str | None, -, optional_repo: bool) -> NotFoundKind:
E                                                                  ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/ReplaceBinaryOperator_Mul_Sub, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -646,7 +646,7 @@
         repo_full_name: str,
         file_path: str,
         ref: str | None = None,
-        *,
+        -,
         missing_repository_is_optional: bool = False,
     ) -> str | None:
         """Get file content from repository.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 649
E       -,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/ReplaceBinaryOperator_Mul_Div, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 / 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7a27f89d7ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7a27f56e49e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
>           raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E           ValueError: Private key exceeds 0.0625 bytes

stampbot/github_client.py:249: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key exceeds 0.0625 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceBinaryOperator_Mul_Div, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -111,7 +111,7 @@
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
 
 
-def _classify_not_found(repo: Repository, ref: str | None, *, optional_repo: bool) -> NotFoundKind:
+def _classify_not_found(repo: Repository, ref: str | None, /, optional_repo: bool) -> NotFoundKind:
     """Decide what a contents 404 means.
 
     An optional fallback repository may itself be missing, which one repository
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceBinaryOperator_Mul_Div, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -646,7 +646,7 @@
         repo_full_name: str,
         file_path: str,
         ref: str | None = None,
-        *,
+        /,
         missing_repository_is_optional: bool = False,
     ) -> str | None:
         """Get file content from repository.
........................................................................ [ 15%]
......................................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________

self = <tests.test_github_client.TestGetRepoFile object at 0x73e982d23390>

    def test_get_repo_file_success(self):
        """Test successful file retrieval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_content = Mock()
            mock_content.decoded_content = b"file content"
            mock_repo = Mock()
            mock_repo.get_contents.return_value = mock_content
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/repo",
                "stampbot.toml",
                ref="main",
            )
E           TypeError: GitHubAppClient.get_repo_file() got some positional-only arguments passed as keyword arguments: 'ref'

tests/test_github_client.py:978: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - TypeError: GitHubAppClient.get_repo_file() got some positional-only arguments passed as keyword arguments: 'ref'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceBinaryOperator_Mul_FloorDiv, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 // 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x712143f9fad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7121407ac9e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
>           raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E           ValueError: Private key exceeds 0 bytes

stampbot/github_client.py:249: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key exceeds 0 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceBinaryOperator_Mul_FloorDiv, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -111,7 +111,7 @@
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
 
 
-def _classify_not_found(repo: Repository, ref: str | None, *, optional_repo: bool) -> NotFoundKind:
+def _classify_not_found(repo: Repository, ref: str | None, //, optional_repo: bool) -> NotFoundKind:
     """Decide what a contents 404 means.
 
     An optional fallback repository may itself be missing, which one repository
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 114
E       def _classify_not_found(repo: Repository, ref: str | None, //, optional_repo: bool) -> NotFoundKind:
E                                                                  ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/ReplaceBinaryOperator_Mul_FloorDiv, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -646,7 +646,7 @@
         repo_full_name: str,
         file_path: str,
         ref: str | None = None,
-        *,
+        //,
         missing_repository_is_optional: bool = False,
     ) -> str | None:
         """Get file content from repository.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 649
E       //,
E       ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/ReplaceBinaryOperator_Mul_Mod, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 % 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x73d1037f3ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x73d1005589e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
>           raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E           ValueError: Private key exceeds 64 bytes

stampbot/github_client.py:249: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key exceeds 64 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceBinaryOperator_Mul_Mod, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -111,7 +111,7 @@
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
 
 
-def _classify_not_found(repo: Repository, ref: str | None, *, optional_repo: bool) -> NotFoundKind:
+def _classify_not_found(repo: Repository, ref: str | None, %, optional_repo: bool) -> NotFoundKind:
     """Decide what a contents 404 means.
 
     An optional fallback repository may itself be missing, which one repository
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 114
E       def _classify_not_found(repo: Repository, ref: str | None, %, optional_repo: bool) -> NotFoundKind:
E                                                                  ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/ReplaceBinaryOperator_Mul_Mod, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -646,7 +646,7 @@
         repo_full_name: str,
         file_path: str,
         ref: str | None = None,
-        *,
+        %,
         missing_repository_is_optional: bool = False,
     ) -> str | None:
         """Get file content from repository.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 649
E       %,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/ReplaceBinaryOperator_Mul_Pow, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 ** 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x74eb26f18d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-913/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x74eb1f550320>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
>                   pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E                   OverflowError: cannot fit 'int' into an index-sized integer

stampbot/github_client.py:241: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - OverflowError: cannot fit 'int' into an index-sized integer
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.38s
operator: core/ReplaceBinaryOperator_Mul_Pow, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -111,7 +111,7 @@
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
 
 
-def _classify_not_found(repo: Repository, ref: str | None, *, optional_repo: bool) -> NotFoundKind:
+def _classify_not_found(repo: Repository, ref: str | None, **, optional_repo: bool) -> NotFoundKind:
     """Decide what a contents 404 means.
 
     An optional fallback repository may itself be missing, which one repository
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 114
E       def _classify_not_found(repo: Repository, ref: str | None, **, optional_repo: bool) -> NotFoundKind:
E                                                                    ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.77s
operator: core/ReplaceBinaryOperator_Mul_Pow, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -646,7 +646,7 @@
         repo_full_name: str,
         file_path: str,
         ref: str | None = None,
-        *,
+        **,
         missing_repository_is_optional: bool = False,
     ) -> str | None:
         """Get file content from repository.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 649
E       **,
E         ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/ReplaceBinaryOperator_Mul_RShift, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 >> 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x708e185fbad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x708e14db49e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
>           raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E           ValueError: Private key exceeds 0 bytes

stampbot/github_client.py:249: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key exceeds 0 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.37s
operator: core/ReplaceBinaryOperator_Mul_RShift, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -111,7 +111,7 @@
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
 
 
-def _classify_not_found(repo: Repository, ref: str | None, *, optional_repo: bool) -> NotFoundKind:
+def _classify_not_found(repo: Repository, ref: str | None, >>, optional_repo: bool) -> NotFoundKind:
     """Decide what a contents 404 means.
 
     An optional fallback repository may itself be missing, which one repository
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 114
E       def _classify_not_found(repo: Repository, ref: str | None, >>, optional_repo: bool) -> NotFoundKind:
E                                                                  ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/ReplaceBinaryOperator_Mul_RShift, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -646,7 +646,7 @@
         repo_full_name: str,
         file_path: str,
         ref: str | None = None,
-        *,
+        >>,
         missing_repository_is_optional: bool = False,
     ) -> str | None:
         """Get file content from repository.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 649
E       >>,
E       ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/ReplaceBinaryOperator_Mul_LShift, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 << 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x7140ac3c4d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-314/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7140a9134320>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
>                   pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E                   OverflowError: cannot fit 'int' into an index-sized integer

stampbot/github_client.py:241: OverflowError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - OverflowError: cannot fit 'int' into an index-sized integer
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceBinaryOperator_Mul_LShift, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -111,7 +111,7 @@
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
 
 
-def _classify_not_found(repo: Repository, ref: str | None, *, optional_repo: bool) -> NotFoundKind:
+def _classify_not_found(repo: Repository, ref: str | None, <<, optional_repo: bool) -> NotFoundKind:
     """Decide what a contents 404 means.
 
     An optional fallback repository may itself be missing, which one repository
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 114
E       def _classify_not_found(repo: Repository, ref: str | None, <<, optional_repo: bool) -> NotFoundKind:
E                                                                  ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/ReplaceBinaryOperator_Mul_LShift, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -646,7 +646,7 @@
         repo_full_name: str,
         file_path: str,
         ref: str | None = None,
-        *,
+        <<,
         missing_repository_is_optional: bool = False,
     ) -> str | None:
         """Get file content from repository.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 649
E       <<,
E       ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.78s
operator: core/ReplaceBinaryOperator_Mul_BitOr, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 | 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceBinaryOperator_Mul_BitOr, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -111,7 +111,7 @@
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
 
 
-def _classify_not_found(repo: Repository, ref: str | None, *, optional_repo: bool) -> NotFoundKind:
+def _classify_not_found(repo: Repository, ref: str | None, |, optional_repo: bool) -> NotFoundKind:
     """Decide what a contents 404 means.
 
     An optional fallback repository may itself be missing, which one repository
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 114
E       def _classify_not_found(repo: Repository, ref: str | None, |, optional_repo: bool) -> NotFoundKind:
E                                                                  ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/ReplaceBinaryOperator_Mul_BitOr, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -646,7 +646,7 @@
         repo_full_name: str,
         file_path: str,
         ref: str | None = None,
-        *,
+        |,
         missing_repository_is_optional: bool = False,
     ) -> str | None:
         """Get file content from repository.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 649
E       |,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/ReplaceBinaryOperator_Mul_BitAnd, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 & 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7dd1d5de7ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7dd1d2b3c9e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
>           raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E           ValueError: Private key exceeds 0 bytes

stampbot/github_client.py:249: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key exceeds 0 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceBinaryOperator_Mul_BitAnd, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -111,7 +111,7 @@
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
 
 
-def _classify_not_found(repo: Repository, ref: str | None, *, optional_repo: bool) -> NotFoundKind:
+def _classify_not_found(repo: Repository, ref: str | None, &, optional_repo: bool) -> NotFoundKind:
     """Decide what a contents 404 means.
 
     An optional fallback repository may itself be missing, which one repository
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 114
E       def _classify_not_found(repo: Repository, ref: str | None, &, optional_repo: bool) -> NotFoundKind:
E                                                                  ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.75s
operator: core/ReplaceBinaryOperator_Mul_BitAnd, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -646,7 +646,7 @@
         repo_full_name: str,
         file_path: str,
         ref: str | None = None,
-        *,
+        &,
         missing_repository_is_optional: bool = False,
     ) -> str | None:
         """Get file content from repository.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 649
E       &,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/ReplaceBinaryOperator_Mul_BitXor, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 ^ 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.43s
operator: core/ReplaceBinaryOperator_Mul_BitXor, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -111,7 +111,7 @@
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
 
 
-def _classify_not_found(repo: Repository, ref: str | None, *, optional_repo: bool) -> NotFoundKind:
+def _classify_not_found(repo: Repository, ref: str | None, ^, optional_repo: bool) -> NotFoundKind:
     """Decide what a contents 404 means.
 
     An optional fallback repository may itself be missing, which one repository
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 114
E       def _classify_not_found(repo: Repository, ref: str | None, ^, optional_repo: bool) -> NotFoundKind:
E                                                                  ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.74s
operator: core/ReplaceBinaryOperator_Mul_BitXor, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -646,7 +646,7 @@
         repo_full_name: str,
         file_path: str,
         ref: str | None = None,
-        *,
+        ^,
         missing_repository_is_optional: bool = False,
     ) -> str | None:
         """Get file content from repository.
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:25: in <module>
    from stampbot.github_client import _sanitize_error
E     File "/home/runner/work/stampbot/stampbot/stampbot/github_client.py", line 649
E       ^,
E       ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.76s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0, definition_name: _repository_is_missing
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -102,7 +102,7 @@
     try:
         repo.complete()
     except GithubException as e:
-        return e.status == 404
+        return e.status != 404
     except Exception:
         return False
     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
.......F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_accepts_missing_optional_repository_with_lazy_client _

self = <tests.test_github_client.TestGetRepoFile object at 0x79127a210d70>

    def test_get_repo_file_accepts_missing_optional_repository_with_lazy_client(self):
        """Test a lazy client confirms the optional repository itself is missing."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_integration_cls.return_value = mock_integration
    
            # Lazy get_repo never raises. The file read and the root read both
            # 404, and only the repository request proves the repo is absent.
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                GithubException(404, {"message": "Not Found"}, None),
            ]
            mock_repo.complete.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/.github",
                "stampbot.toml",
                missing_repository_is_optional=True,
            )

tests/test_github_client.py:1236: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='133120209922304'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:08:54.956505Z [warning  ] GitHub could not read stampbot.toml from owner/.github (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/.github (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:08:54.956505Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_accepts_missing_optional_repository_with_lazy_client - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 151 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 1, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -706,7 +706,7 @@
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
-                if e.status == 404 and repo is not None:
+                if e.status != 404 and repo is not None:
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x7b9bf7c495b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='135909749322624'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:03:30.374185Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:03:30.374185Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -710,7 +710,7 @@
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
-                repository_is_optionally_missing = not_found == "repository"
+                repository_is_optionally_missing = not_found != "repository"
                 file_is_confirmed_missing = not_found == "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
.F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_propagates_unconfirmed_not_found[root_result0] _

self = <tests.test_github_client.TestGetRepoFile object at 0x71fc9b5f16e0>
root_result = GithubException(404 {"message": "Not Found"})

    @pytest.mark.parametrize(
        "root_result",
        [
            GithubException(404, {"message": "Not Found"}, None),
            GithubException(403, {"message": "Forbidden"}, None),
            TimeoutError("GitHub root read timed out"),
            Mock(),
        ],
    )
    def test_get_repo_file_propagates_unconfirmed_not_found(self, root_result):
        """Test a contents 404 fails closed unless the same ref remains readable."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            first = GithubException(404, {"message": "Not Found"}, None)
            mock_repo.get_contents.side_effect = [first, root_result]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(GithubException) as error:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE GithubException

tests/test_github_client.py:1110: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_unconfirmed_not_found[root_result0] - Failed: DID NOT RAISE GithubException
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 145 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 3, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -711,7 +711,7 @@
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
                 repository_is_optionally_missing = not_found == "repository"
-                file_is_confirmed_missing = not_found == "file"
+                file_is_confirmed_missing = not_found != "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x735e4034d5b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='126848585216896'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:04:32.796259Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:04:32.796259Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 4, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -812,7 +812,7 @@
 
                 bot_reviews: list[BotReview] = []
                 for review in pr.get_reviews():
-                    if review.user.login == bot_user and review.state in (
+                    if review.user.login != bot_user and review.state in (
                         "APPROVED",
                         "DISMISSED",
                     ):
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x74e88aefb4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert [{'id': 456, ...2080237648'>}] == [{'id': 123, ...d': 'oldsha'}]
E             
E             At index 0 diff: {'id': 456, 'state': 'APPROVED', 'commit_id': <Mock name='mock.commit_id' id='128542080237648'>} != {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
E             Right contains one more item: {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   {
E             -         'id': 123,
E             ?               ^^^
E             +         'id': 456,
E             ?               ^^^
E                       'state': 'APPROVED',
E             +         'commit_id': <Mock name='mock.commit_id' id='128542080237648'>,
E             -         'commit_id': 'headsha',
E             -     },
E             -     {
E             -         'id': 124,
E             -         'state': 'DISMISSED',
E             -         'commit_id': 'oldsha',
E                   },
E               ]

tests/test_github_client.py:1481: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert [{'id': 456, ...2080237648'>}] == [{'id': 123, ...d': 'oldsha'}]
  
  At index 0 diff: {'id': 456, 'state': 'APPROVED', 'commit_id': <Mock name='mock.commit_id' id='128542080237648'>} != {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
  Right contains one more item: {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        {
  -         'id': 123,
  ?               ^^^
  +         'id': 456,
  ?               ^^^
            'state': 'APPROVED',
  +         'commit_id': <Mock name='mock.commit_id' id='128542080237648'>,
  -         'commit_id': 'headsha',
  -     },
  -     {
  -         'id': 124,
  -         'state': 'DISMISSED',
  -         'commit_id': 'oldsha',
        },
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 5, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1067,7 +1067,7 @@
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
-                if e.status == 404:
+                if e.status != 404:
                     add_span_attributes(span, {"github.label_found": False})
                     set_span_ok(span)
                     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x7d6ceb0ffd90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "missing")
    
>           assert result is False
E           assert None is False

tests/test_github_client.py:1977: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0, definition_name: _repository_is_missing
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -102,7 +102,7 @@
     try:
         repo.complete()
     except GithubException as e:
-        return e.status == 404
+        return e.status < 404
     except Exception:
         return False
     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
.......F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_accepts_missing_optional_repository_with_lazy_client _

self = <tests.test_github_client.TestGetRepoFile object at 0x79289b40cd70>

    def test_get_repo_file_accepts_missing_optional_repository_with_lazy_client(self):
        """Test a lazy client confirms the optional repository itself is missing."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_integration_cls.return_value = mock_integration
    
            # Lazy get_repo never raises. The file read and the root read both
            # 404, and only the repository request proves the repo is absent.
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                GithubException(404, {"message": "Not Found"}, None),
            ]
            mock_repo.complete.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/.github",
                "stampbot.toml",
                missing_repository_is_optional=True,
            )

tests/test_github_client.py:1236: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='133215186675968'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:10:32.351365Z [warning  ] GitHub could not read stampbot.toml from owner/.github (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/.github (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:10:32.351365Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_accepts_missing_optional_repository_with_lazy_client - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 151 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 1, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -706,7 +706,7 @@
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
-                if e.status == 404 and repo is not None:
+                if e.status < 404 and repo is not None:
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x7c6f4385d5b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='136817189401472'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:15:13.751010Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:15:13.751010Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -710,7 +710,7 @@
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
-                repository_is_optionally_missing = not_found == "repository"
+                repository_is_optionally_missing = not_found < "repository"
                 file_is_confirmed_missing = not_found == "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
.......F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_accepts_missing_optional_repository_with_lazy_client _

self = <tests.test_github_client.TestGetRepoFile object at 0x7d1662018d70>

    def test_get_repo_file_accepts_missing_optional_repository_with_lazy_client(self):
        """Test a lazy client confirms the optional repository itself is missing."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_integration_cls.return_value = mock_integration
    
            # Lazy get_repo never raises. The file read and the root read both
            # 404, and only the repository request proves the repo is absent.
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                GithubException(404, {"message": "Not Found"}, None),
            ]
            mock_repo.complete.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/.github",
                "stampbot.toml",
                missing_repository_is_optional=True,
            )

tests/test_github_client.py:1236: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='137535030946048'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:13:07.433453Z [warning  ] GitHub could not read stampbot.toml from owner/.github (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/.github (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:13:07.433453Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_accepts_missing_optional_repository_with_lazy_client - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 151 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 3, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -711,7 +711,7 @@
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
                 repository_is_optionally_missing = not_found == "repository"
-                file_is_confirmed_missing = not_found == "file"
+                file_is_confirmed_missing = not_found < "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x7a78310415b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='134656580760448'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:50:51.715888Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:50:51.715888Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 4, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -812,7 +812,7 @@
 
                 bot_reviews: list[BotReview] = []
                 for review in pr.get_reviews():
-                    if review.user.login == bot_user and review.state in (
+                    if review.user.login < bot_user and review.state in (
                         "APPROVED",
                         "DISMISSED",
                     ):
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7de91838b4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert [{'id': 456, ...0057962576'>}] == [{'id': 123, ...d': 'oldsha'}]
E             
E             At index 0 diff: {'id': 456, 'state': 'APPROVED', 'commit_id': <Mock name='mock.commit_id' id='138440057962576'>} != {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
E             Right contains one more item: {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   {
E             -         'id': 123,
E             ?               ^^^
E             +         'id': 456,
E             ?               ^^^
E                       'state': 'APPROVED',
E             +         'commit_id': <Mock name='mock.commit_id' id='138440057962576'>,
E             -         'commit_id': 'headsha',
E             -     },
E             -     {
E             -         'id': 124,
E             -         'state': 'DISMISSED',
E             -         'commit_id': 'oldsha',
E                   },
E               ]

tests/test_github_client.py:1481: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert [{'id': 456, ...0057962576'>}] == [{'id': 123, ...d': 'oldsha'}]
  
  At index 0 diff: {'id': 456, 'state': 'APPROVED', 'commit_id': <Mock name='mock.commit_id' id='138440057962576'>} != {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
  Right contains one more item: {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        {
  -         'id': 123,
  ?               ^^^
  +         'id': 456,
  ?               ^^^
            'state': 'APPROVED',
  +         'commit_id': <Mock name='mock.commit_id' id='138440057962576'>,
  -         'commit_id': 'headsha',
  -     },
  -     {
  -         'id': 124,
  -         'state': 'DISMISSED',
  -         'commit_id': 'oldsha',
        },
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 5, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1067,7 +1067,7 @@
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
-                if e.status == 404:
+                if e.status < 404:
                     add_span_attributes(span, {"github.label_found": False})
                     set_span_ok(span)
                     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x774d6a797d90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "missing")
    
>           assert result is False
E           assert None is False

tests/test_github_client.py:1977: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0, definition_name: _repository_is_missing
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -102,7 +102,7 @@
     try:
         repo.complete()
     except GithubException as e:
-        return e.status == 404
+        return e.status <= 404
     except Exception:
         return False
     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................F
=================================== FAILURES ===================================
_ TestRepositoryIsMissing.test_repository_is_missing_is_false_unless_github_says_404[outcome1] _

self = <tests.test_github_client.TestRepositoryIsMissing object at 0x729b5914e190>
outcome = GithubException(403 {"message": "Forbidden"})

    @pytest.mark.parametrize(
        "outcome",
        [
            None,
            GithubException(403, {"message": "Forbidden"}, None),
            TimeoutError("GitHub read timed out"),
        ],
    )
    def test_repository_is_missing_is_false_unless_github_says_404(self, outcome):
        """Test success and every other failure keep the fail-closed path."""
        from stampbot.github_client import _repository_is_missing
    
        repo = Mock()
        if outcome is not None:
            repo.complete.side_effect = outcome
    
>       assert _repository_is_missing(repo) is False
E       AssertionError: assert True is False
E        +  where True = <function _repository_is_missing at 0x729b5621d7a0>(<Mock id='126011506184880'>)

tests/test_github_client.py:2317: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepositoryIsMissing::test_repository_is_missing_is_false_unless_github_says_404[outcome1] - AssertionError: assert True is False
 +  where True = <function _repository_is_missing at 0x729b5621d7a0>(<Mock id='126011506184880'>)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 184 passed, 3 deselected, 1 warning in 1.51s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 1, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -706,7 +706,7 @@
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
-                if e.status == 404 and repo is not None:
+                if e.status <= 404 and repo is not None:
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -710,7 +710,7 @@
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
-                repository_is_optionally_missing = not_found == "repository"
+                repository_is_optionally_missing = not_found <= "repository"
                 file_is_confirmed_missing = not_found == "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 3, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -711,7 +711,7 @@
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
                 repository_is_optionally_missing = not_found == "repository"
-                file_is_confirmed_missing = not_found == "file"
+                file_is_confirmed_missing = not_found <= "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 4, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -812,7 +812,7 @@
 
                 bot_reviews: list[BotReview] = []
                 for review in pr.get_reviews():
-                    if review.user.login == bot_user and review.state in (
+                    if review.user.login <= bot_user and review.state in (
                         "APPROVED",
                         "DISMISSED",
                     ):
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7b7b10f9f4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert [{'id': 123, ...8440242256'>}] == [{'id': 123, ...d': 'oldsha'}]
E             
E             Left contains one more item: {'id': 456, 'state': 'APPROVED', 'commit_id': <Mock name='mock.commit_id' id='135768440242256'>}
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   {
E                       'id': 123,
E                       'state': 'APPROVED',
E                       'commit_id': 'headsha',
E                   },
E                   {
E                       'id': 124,
E                       'state': 'DISMISSED',
E                       'commit_id': 'oldsha',
E                   },
E             +     {
E             +         'id': 456,
E             +         'state': 'APPROVED',
E             +         'commit_id': <Mock name='mock.commit_id' id='135768440242256'>,
E             +     },
E               ]

tests/test_github_client.py:1481: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert [{'id': 123, ...8440242256'>}] == [{'id': 123, ...d': 'oldsha'}]
  
  Left contains one more item: {'id': 456, 'state': 'APPROVED', 'commit_id': <Mock name='mock.commit_id' id='135768440242256'>}
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        {
            'id': 123,
            'state': 'APPROVED',
            'commit_id': 'headsha',
        },
        {
            'id': 124,
            'state': 'DISMISSED',
            'commit_id': 'oldsha',
        },
  +     {
  +         'id': 456,
  +         'state': 'APPROVED',
  +         'commit_id': <Mock name='mock.commit_id' id='135768440242256'>,
  +     },
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 5, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1067,7 +1067,7 @@
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
-                if e.status == 404:
+                if e.status <= 404:
                     add_span_attributes(span, {"github.label_found": False})
                     set_span_ok(span)
                     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0, definition_name: _repository_is_missing
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -102,7 +102,7 @@
     try:
         repo.complete()
     except GithubException as e:
-        return e.status == 404
+        return e.status > 404
     except Exception:
         return False
     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
.......F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_accepts_missing_optional_repository_with_lazy_client _

self = <tests.test_github_client.TestGetRepoFile object at 0x7139131bcd70>

    def test_get_repo_file_accepts_missing_optional_repository_with_lazy_client(self):
        """Test a lazy client confirms the optional repository itself is missing."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_integration_cls.return_value = mock_integration
    
            # Lazy get_repo never raises. The file read and the root read both
            # 404, and only the repository request proves the repo is absent.
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                GithubException(404, {"message": "Not Found"}, None),
            ]
            mock_repo.complete.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/.github",
                "stampbot.toml",
                missing_repository_is_optional=True,
            )

tests/test_github_client.py:1236: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='124489826216192'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:08:33.201555Z [warning  ] GitHub could not read stampbot.toml from owner/.github (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/.github (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:08:33.201555Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_accepts_missing_optional_repository_with_lazy_client - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 151 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 1, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -706,7 +706,7 @@
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
-                if e.status == 404 and repo is not None:
+                if e.status > 404 and repo is not None:
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x771a5ee495b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='130955088835456'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:36:04.008434Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:36:04.008434Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -710,7 +710,7 @@
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
-                repository_is_optionally_missing = not_found == "repository"
+                repository_is_optionally_missing = not_found > "repository"
                 file_is_confirmed_missing = not_found == "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
.F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_propagates_unconfirmed_not_found[root_result0] _

self = <tests.test_github_client.TestGetRepoFile object at 0x70472f0456e0>
root_result = GithubException(404 {"message": "Not Found"})

    @pytest.mark.parametrize(
        "root_result",
        [
            GithubException(404, {"message": "Not Found"}, None),
            GithubException(403, {"message": "Forbidden"}, None),
            TimeoutError("GitHub root read timed out"),
            Mock(),
        ],
    )
    def test_get_repo_file_propagates_unconfirmed_not_found(self, root_result):
        """Test a contents 404 fails closed unless the same ref remains readable."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            first = GithubException(404, {"message": "Not Found"}, None)
            mock_repo.get_contents.side_effect = [first, root_result]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(GithubException) as error:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE GithubException

tests/test_github_client.py:1110: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_unconfirmed_not_found[root_result0] - Failed: DID NOT RAISE GithubException
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 145 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 3, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -711,7 +711,7 @@
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
                 repository_is_optionally_missing = not_found == "repository"
-                file_is_confirmed_missing = not_found == "file"
+                file_is_confirmed_missing = not_found > "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x7b4089c415b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='135517063558016'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:13:48.418704Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:13:48.418704Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 4, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -812,7 +812,7 @@
 
                 bot_reviews: list[BotReview] = []
                 for review in pr.get_reviews():
-                    if review.user.login == bot_user and review.state in (
+                    if review.user.login > bot_user and review.state in (
                         "APPROVED",
                         "DISMISSED",
                     ):
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x75745039b4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert [] == [{'id': 123, ...d': 'oldsha'}]
E             
E             Right contains 2 more items, first extra item: {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E             + []
E             - [
E             -     {
E             -         'id': 123,
E             -         'state': 'APPROVED',
E             -         'commit_id': 'headsha',
E             -     },
E             -     {
E             -         'id': 124,
E             -         'state': 'DISMISSED',
E             -         'commit_id': 'oldsha',
E             -     },
E             - ]

tests/test_github_client.py:1481: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert [] == [{'id': 123, ...d': 'oldsha'}]
  
  Right contains 2 more items, first extra item: {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     {
  -         'id': 123,
  -         'state': 'APPROVED',
  -         'commit_id': 'headsha',
  -     },
  -     {
  -         'id': 124,
  -         'state': 'DISMISSED',
  -         'commit_id': 'oldsha',
  -     },
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 5, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1067,7 +1067,7 @@
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
-                if e.status == 404:
+                if e.status > 404:
                     add_span_attributes(span, {"github.label_found": False})
                     set_span_ok(span)
                     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x7f2da147bd90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "missing")
    
>           assert result is False
E           assert None is False

tests/test_github_client.py:1977: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0, definition_name: _repository_is_missing
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -102,7 +102,7 @@
     try:
         repo.complete()
     except GithubException as e:
-        return e.status == 404
+        return e.status >= 404
     except Exception:
         return False
     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 1, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -706,7 +706,7 @@
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
-                if e.status == 404 and repo is not None:
+                if e.status >= 404 and repo is not None:
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -710,7 +710,7 @@
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
-                repository_is_optionally_missing = not_found == "repository"
+                repository_is_optionally_missing = not_found >= "repository"
                 file_is_confirmed_missing = not_found == "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
.F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_propagates_unconfirmed_not_found[root_result0] _

self = <tests.test_github_client.TestGetRepoFile object at 0x7b7604b456e0>
root_result = GithubException(404 {"message": "Not Found"})

    @pytest.mark.parametrize(
        "root_result",
        [
            GithubException(404, {"message": "Not Found"}, None),
            GithubException(403, {"message": "Forbidden"}, None),
            TimeoutError("GitHub root read timed out"),
            Mock(),
        ],
    )
    def test_get_repo_file_propagates_unconfirmed_not_found(self, root_result):
        """Test a contents 404 fails closed unless the same ref remains readable."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            first = GithubException(404, {"message": "Not Found"}, None)
            mock_repo.get_contents.side_effect = [first, root_result]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(GithubException) as error:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE GithubException

tests/test_github_client.py:1110: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_unconfirmed_not_found[root_result0] - Failed: DID NOT RAISE GithubException
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 145 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 3, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -711,7 +711,7 @@
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
                 repository_is_optionally_missing = not_found == "repository"
-                file_is_confirmed_missing = not_found == "file"
+                file_is_confirmed_missing = not_found >= "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_propagates_unconfirmed_not_found[root_result0] _

self = <tests.test_github_client.TestGetRepoFile object at 0x72d09b5056e0>
root_result = GithubException(404 {"message": "Not Found"})

    @pytest.mark.parametrize(
        "root_result",
        [
            GithubException(404, {"message": "Not Found"}, None),
            GithubException(403, {"message": "Forbidden"}, None),
            TimeoutError("GitHub root read timed out"),
            Mock(),
        ],
    )
    def test_get_repo_file_propagates_unconfirmed_not_found(self, root_result):
        """Test a contents 404 fails closed unless the same ref remains readable."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            first = GithubException(404, {"message": "Not Found"}, None)
            mock_repo.get_contents.side_effect = [first, root_result]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(GithubException) as error:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE GithubException

tests/test_github_client.py:1110: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_unconfirmed_not_found[root_result0] - Failed: DID NOT RAISE GithubException
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 145 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 4, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -812,7 +812,7 @@
 
                 bot_reviews: list[BotReview] = []
                 for review in pr.get_reviews():
-                    if review.user.login == bot_user and review.state in (
+                    if review.user.login >= bot_user and review.state in (
                         "APPROVED",
                         "DISMISSED",
                     ):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 5, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1067,7 +1067,7 @@
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
-                if e.status == 404:
+                if e.status >= 404:
                     add_span_attributes(span, {"github.label_found": False})
                     set_span_ok(span)
                     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
...........................F
=================================== FAILURES ===================================
____________ TestRepoHasLabel.test_repo_has_label_github_exception _____________

self = <tests.test_github_client.TestRepoHasLabel object at 0x712affe35cd0>

    def test_repo_has_label_github_exception(self):
        """Test repo_has_label returns None on GitHubException errors."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(500, {"message": "Error"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "autoapprove")
    
>           assert result is None
E           assert False is None

tests/test_github_client.py:2015: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_github_exception - assert False is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 171 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -710,7 +710,7 @@
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
-                repository_is_optionally_missing = not_found == "repository"
+                repository_is_optionally_missing = not_found is "repository"
                 file_is_confirmed_missing = not_found == "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/github_client.py:713
  /home/runner/work/stampbot/stampbot/stampbot/github_client.py:713: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    repository_is_optionally_missing = not_found is "repository"

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.32s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 1, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -711,7 +711,7 @@
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
                 repository_is_optionally_missing = not_found == "repository"
-                file_is_confirmed_missing = not_found == "file"
+                file_is_confirmed_missing = not_found is "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/github_client.py:714
  /home/runner/work/stampbot/stampbot/stampbot/github_client.py:714: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    file_is_confirmed_missing = not_found is "file"

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.30s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 2, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -812,7 +812,7 @@
 
                 bot_reviews: list[BotReview] = []
                 for review in pr.get_reviews():
-                    if review.user.login == bot_user and review.state in (
+                    if review.user.login is bot_user and review.state in (
                         "APPROVED",
                         "DISMISSED",
                     ):
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x790d06d7f4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert [] == [{'id': 123, ...d': 'oldsha'}]
E             
E             Right contains 2 more items, first extra item: {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E             + []
E             - [
E             -     {
E             -         'id': 123,
E             -         'state': 'APPROVED',
E             -         'commit_id': 'headsha',
E             -     },
E             -     {
E             -         'id': 124,
E             -         'state': 'DISMISSED',
E             -         'commit_id': 'oldsha',
E             -     },
E             - ]

tests/test_github_client.py:1481: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert [] == [{'id': 123, ...d': 'oldsha'}]
  
  Right contains 2 more items, first extra item: {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     {
  -         'id': 123,
  -         'state': 'APPROVED',
  -         'commit_id': 'headsha',
  -     },
  -     {
  -         'id': 124,
  -         'state': 'DISMISSED',
  -         'commit_id': 'oldsha',
  -     },
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -710,7 +710,7 @@
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
-                repository_is_optionally_missing = not_found == "repository"
+                repository_is_optionally_missing = not_found is not "repository"
                 file_is_confirmed_missing = not_found == "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
.F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_propagates_unconfirmed_not_found[root_result0] _

self = <tests.test_github_client.TestGetRepoFile object at 0x7a2317fd56e0>
root_result = GithubException(404 {"message": "Not Found"})

    @pytest.mark.parametrize(
        "root_result",
        [
            GithubException(404, {"message": "Not Found"}, None),
            GithubException(403, {"message": "Forbidden"}, None),
            TimeoutError("GitHub root read timed out"),
            Mock(),
        ],
    )
    def test_get_repo_file_propagates_unconfirmed_not_found(self, root_result):
        """Test a contents 404 fails closed unless the same ref remains readable."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            first = GithubException(404, {"message": "Not Found"}, None)
            mock_repo.get_contents.side_effect = [first, root_result]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(GithubException) as error:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE GithubException

tests/test_github_client.py:1110: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/github_client.py:713
  /home/runner/work/stampbot/stampbot/stampbot/github_client.py:713: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    repository_is_optionally_missing = not_found is not "repository"

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_unconfirmed_not_found[root_result0] - Failed: DID NOT RAISE GithubException
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 145 passed, 3 deselected, 2 warnings in 1.43s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 1, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -711,7 +711,7 @@
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
                 repository_is_optionally_missing = not_found == "repository"
-                file_is_confirmed_missing = not_found == "file"
+                file_is_confirmed_missing = not_found is not "file"
                 if repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x7f0c29d495b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='139690161845120'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:35:13.479985Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:35:13.479985Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/github_client.py:714
  /home/runner/work/stampbot/stampbot/stampbot/github_client.py:714: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    file_is_confirmed_missing = not_found is not "file"

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 2 warnings in 1.43s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 2, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -812,7 +812,7 @@
 
                 bot_reviews: list[BotReview] = []
                 for review in pr.get_reviews():
-                    if review.user.login == bot_user and review.state in (
+                    if review.user.login is not bot_user and review.state in (
                         "APPROVED",
                         "DISMISSED",
                     ):
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7af51cdaf4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert [{'id': 123, ...3114837072'>}] == [{'id': 123, ...d': 'oldsha'}]
E             
E             Left contains one more item: {'id': 456, 'state': 'APPROVED', 'commit_id': <Mock name='mock.commit_id' id='135193114837072'>}
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   {
E                       'id': 123,
E                       'state': 'APPROVED',
E                       'commit_id': 'headsha',
E                   },
E                   {
E                       'id': 124,
E                       'state': 'DISMISSED',
E                       'commit_id': 'oldsha',
E                   },
E             +     {
E             +         'id': 456,
E             +         'state': 'APPROVED',
E             +         'commit_id': <Mock name='mock.commit_id' id='135193114837072'>,
E             +     },
E               ]

tests/test_github_client.py:1481: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert [{'id': 123, ...3114837072'>}] == [{'id': 123, ...d': 'oldsha'}]
  
  Left contains one more item: {'id': 456, 'state': 'APPROVED', 'commit_id': <Mock name='mock.commit_id' id='135193114837072'>}
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        {
            'id': 123,
            'state': 'APPROVED',
            'commit_id': 'headsha',
        },
        {
            'id': 124,
            'state': 'DISMISSED',
            'commit_id': 'oldsha',
        },
  +     {
  +         'id': 456,
  +         'state': 'APPROVED',
  +         'commit_id': <Mock name='mock.commit_id' id='135193114837072'>,
  +     },
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceComparisonOperator_NotEq_Eq, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[-1] == expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x79ae37df7ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x79ae345a49e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] == expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceComparisonOperator_NotEq_Eq, occurrence: 1, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status == 422 or pr.get_review(review_id).state != "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
..................................................................F
=================================== FAILURES ===================================
____ TestDismissApproval.test_dismiss_approval_already_dismissed_is_success ____

self = <tests.test_github_client.TestDismissApproval object at 0x733de7913110>

    def test_dismiss_approval_already_dismissed_is_success(self):
        """Test GitHub's 422 for an already dismissed review counts as done."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "DISMISSED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Can not dismiss a dismissed pull request review"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is True
E           AssertionError: assert False is True
E            +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x733de4723bb0>.dismiss_approval

tests/test_github_client.py:828: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:09:45.475865Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"} [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}, 'event': 'Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"}', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:09:45.475865Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_already_dismissed_is_success - AssertionError: assert False is True
 +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x733de4723bb0>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceComparisonOperator_NotEq_Eq, occurrence: 2, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status != 422 or pr.get_review(review_id).state == "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
..................................................................F
=================================== FAILURES ===================================
____ TestDismissApproval.test_dismiss_approval_already_dismissed_is_success ____

self = <tests.test_github_client.TestDismissApproval object at 0x75c43affb110>

    def test_dismiss_approval_already_dismissed_is_success(self):
        """Test GitHub's 422 for an already dismissed review counts as done."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "DISMISSED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Can not dismiss a dismissed pull request review"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is True
E           AssertionError: assert False is True
E            +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x75c437a27c50>.dismiss_approval

tests/test_github_client.py:828: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:02:27.289861Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"} [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}, 'event': 'Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"}', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:02:27.289861Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_already_dismissed_is_success - AssertionError: assert False is True
 +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x75c437a27c50>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceComparisonOperator_NotEq_Lt, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[-1] < expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceComparisonOperator_NotEq_Lt, occurrence: 1, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status < 422 or pr.get_review(review_id).state != "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.31s
operator: core/ReplaceComparisonOperator_NotEq_Lt, occurrence: 2, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status != 422 or pr.get_review(review_id).state < "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceComparisonOperator_NotEq_LtE, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[-1] <= expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7967c71c3ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7967bfeb89e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] <= expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceComparisonOperator_NotEq_LtE, occurrence: 1, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status <= 422 or pr.get_review(review_id).state != "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
..................................................................F
=================================== FAILURES ===================================
____ TestDismissApproval.test_dismiss_approval_already_dismissed_is_success ____

self = <tests.test_github_client.TestDismissApproval object at 0x73f63177b110>

    def test_dismiss_approval_already_dismissed_is_success(self):
        """Test GitHub's 422 for an already dismissed review counts as done."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "DISMISSED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Can not dismiss a dismissed pull request review"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is True
E           AssertionError: assert False is True
E            +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x73f62e5cbc50>.dismiss_approval

tests/test_github_client.py:828: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:16:17.182822Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"} [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}, 'event': 'Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"}', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:16:17.182822Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_already_dismissed_is_success - AssertionError: assert False is True
 +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x73f62e5cbc50>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceComparisonOperator_NotEq_LtE, occurrence: 2, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status != 422 or pr.get_review(review_id).state <= "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
..................................................................F
=================================== FAILURES ===================================
____ TestDismissApproval.test_dismiss_approval_already_dismissed_is_success ____

self = <tests.test_github_client.TestDismissApproval object at 0x70967d597110>

    def test_dismiss_approval_already_dismissed_is_success(self):
        """Test GitHub's 422 for an already dismissed review counts as done."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "DISMISSED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Can not dismiss a dismissed pull request review"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is True
E           AssertionError: assert False is True
E            +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x70967a3e7c50>.dismiss_approval

tests/test_github_client.py:828: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:33:07.623395Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"} [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}, 'event': 'Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"}', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:33:07.623395Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_already_dismissed_is_success - AssertionError: assert False is True
 +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x70967a3e7c50>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceComparisonOperator_NotEq_Gt, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[-1] > expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
..............................................F
=================================== FAILURES ===================================
______ TestLoadPrivateKey.test_load_private_key_rejects_mismatched_footer ______

self = <tests.test_github_client.TestLoadPrivateKey object at 0x76dd7a5cef30>

    def test_load_private_key_rejects_mismatched_footer(self):
        """Test the PEM header and footer must describe the same key type."""
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = (
                "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----"
            )
    
            from stampbot.github_client import GitHubAppClient
    
>           with pytest.raises(ValueError, match="footer does not match"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE ValueError

tests/test_github_client.py:271: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_rejects_mismatched_footer - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 118 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceComparisonOperator_NotEq_Gt, occurrence: 1, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status > 422 or pr.get_review(review_id).state != "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceComparisonOperator_NotEq_Gt, occurrence: 2, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status != 422 or pr.get_review(review_id).state > "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
...................................................................F
=================================== FAILURES ===================================
_ TestDismissApproval.test_dismiss_approval_422_with_review_still_approved_fails _

self = <tests.test_github_client.TestDismissApproval object at 0x7f3d087ed350>

    def test_dismiss_approval_422_with_review_still_approved_fails(self):
        """Test a 422 that leaves the review approved is reported as a failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "APPROVED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Validation Failed"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is False
E           AssertionError: assert True is False
E            +  where True = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7f3d0541fbb0>.dismiss_approval

tests/test_github_client.py:864: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:40:59.418778Z [info     ] Review 789 on PR #42 in owner/repo was already dismissed [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=info extra={'repo': 'owner/repo', 'pr_number': 42, 'review_id': 789}
2026-11-04T04:40:59.418950Z [info     ] Dismissed approval for PR #42 in owner/repo [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=info extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'review_id': 789}
------------------------------ Captured log call -------------------------------
INFO     stampbot.github_client:github_client.py:590 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'review_id': 789}, 'event': 'Review 789 on PR #42 in owner/repo was already dismissed', 'logger': 'stampbot.github_client', 'level': 'info', 'timestamp': '2026-11-04T04:40:59.418778Z'}
INFO     stampbot.github_client:github_client.py:611 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'review_id': 789}, 'event': 'Dismissed approval for PR #42 in owner/repo', 'logger': 'stampbot.github_client', 'level': 'info', 'timestamp': '2026-11-04T04:40:59.418950Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_422_with_review_still_approved_fails - AssertionError: assert True is False
 +  where True = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7f3d0541fbb0>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 139 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceComparisonOperator_NotEq_GtE, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[-1] >= expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7d1eab41bad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7d1ea2a9c8d0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] >= expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.36s
operator: core/ReplaceComparisonOperator_NotEq_GtE, occurrence: 1, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status >= 422 or pr.get_review(review_id).state != "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
..................................................................F
=================================== FAILURES ===================================
____ TestDismissApproval.test_dismiss_approval_already_dismissed_is_success ____

self = <tests.test_github_client.TestDismissApproval object at 0x7f7853d7b110>

    def test_dismiss_approval_already_dismissed_is_success(self):
        """Test GitHub's 422 for an already dismissed review counts as done."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "DISMISSED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Can not dismiss a dismissed pull request review"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is True
E           AssertionError: assert False is True
E            +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7f7850abbc50>.dismiss_approval

tests/test_github_client.py:828: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:00:35.184191Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"} [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}, 'event': 'Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"}', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:00:35.184191Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_already_dismissed_is_success - AssertionError: assert False is True
 +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7f7850abbc50>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceComparisonOperator_NotEq_GtE, occurrence: 2, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status != 422 or pr.get_review(review_id).state >= "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
..................................................................F
=================================== FAILURES ===================================
____ TestDismissApproval.test_dismiss_approval_already_dismissed_is_success ____

self = <tests.test_github_client.TestDismissApproval object at 0x724ff4b2b110>

    def test_dismiss_approval_already_dismissed_is_success(self):
        """Test GitHub's 422 for an already dismissed review counts as done."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "DISMISSED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Can not dismiss a dismissed pull request review"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is True
E           AssertionError: assert False is True
E            +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x724ff181fc50>.dismiss_approval

tests/test_github_client.py:828: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:43:14.717146Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"} [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}, 'event': 'Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"}', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:43:14.717146Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_already_dismissed_is_success - AssertionError: assert False is True
 +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x724ff181fc50>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceComparisonOperator_NotEq_Is, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[-1] is expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
..............................................F
=================================== FAILURES ===================================
______ TestLoadPrivateKey.test_load_private_key_rejects_mismatched_footer ______

self = <tests.test_github_client.TestLoadPrivateKey object at 0x74047ed32f30>

    def test_load_private_key_rejects_mismatched_footer(self):
        """Test the PEM header and footer must describe the same key type."""
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = (
                "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----"
            )
    
            from stampbot.github_client import GitHubAppClient
    
>           with pytest.raises(ValueError, match="footer does not match"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE ValueError

tests/test_github_client.py:271: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_rejects_mismatched_footer - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 118 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceComparisonOperator_NotEq_Is, occurrence: 1, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status != 422 or pr.get_review(review_id).state is "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
..................................................................F
=================================== FAILURES ===================================
____ TestDismissApproval.test_dismiss_approval_already_dismissed_is_success ____

self = <tests.test_github_client.TestDismissApproval object at 0x722707097110>

    def test_dismiss_approval_already_dismissed_is_success(self):
        """Test GitHub's 422 for an already dismissed review counts as done."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "DISMISSED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Can not dismiss a dismissed pull request review"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is True
E           AssertionError: assert False is True
E            +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7226ff72bbb0>.dismiss_approval

tests/test_github_client.py:828: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:03:19.023624Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"} [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}, 'event': 'Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"}', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:03:19.023624Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/github_client.py:588
  /home/runner/work/stampbot/stampbot/stampbot/github_client.py:588: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if e.status != 422 or pr.get_review(review_id).state is "DISMISSED":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_already_dismissed_is_success - AssertionError: assert False is True
 +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7226ff72bbb0>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected, 2 warnings in 1.39s
operator: core/ReplaceComparisonOperator_NotEq_IsNot, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[-1] is not expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x712329affad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x71232681c9e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] is not expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceComparisonOperator_NotEq_IsNot, occurrence: 1, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status != 422 or pr.get_review(review_id).state is not "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/github_client.py:588
  /home/runner/work/stampbot/stampbot/stampbot/github_client.py:588: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if e.status != 422 or pr.get_review(review_id).state is not "DISMISSED":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.40s
operator: core/ReplaceComparisonOperator_Lt_Eq, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if len(pem_lines) == 3 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7463984fbad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7463952509e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) == 3 or not pem_lines[0].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceComparisonOperator_Lt_Eq, occurrence: 1, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining == 0 or limit < 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceComparisonOperator_Lt_Eq, occurrence: 2, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining < 0 or limit == 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_Lt_NotEq, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if len(pem_lines) != 3 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_Lt_NotEq, occurrence: 1, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining != 0 or limit < 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................F
=================================== FAILURES ===================================
_ TestRateLimitLabelPruning.test_prune_removes_labels_for_evicted_installations _

self = <tests.test_github_client.TestRateLimitLabelPruning object at 0x7843b399a5d0>

    def test_prune_removes_labels_for_evicted_installations(self):
        """Test gauge label sets follow the credential cache, so churn cannot grow them."""
        from stampbot.github_client import GitHubAppClient
        from stampbot.metrics import github_api_rate_limit_limit, github_api_rate_limit_remaining
    
        client = GitHubAppClient()
        for installation_id in (9001, 9002):
            fake = Mock()
            fake.requester.rate_limiting = (10, 5000)
            client._update_rate_limit_metrics(fake, installation_id)
>       assert client._rate_limit_installations == {"9001", "9002"}
E       AssertionError: assert set() == {'9001', '9002'}
E         
E         Extra items in the right set:
E         '9002'
E         '9001'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + set()
E         - {
E         -     '9001',
E         -     '9002',
E         - }

tests/test_github_client.py:519: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRateLimitLabelPruning::test_prune_removes_labels_for_evicted_installations - AssertionError: assert set() == {'9001', '9002'}
  
  Extra items in the right set:
  '9002'
  '9001'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + set()
  - {
  -     '9001',
  -     '9002',
  - }
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 128 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceComparisonOperator_Lt_NotEq, occurrence: 2, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining < 0 or limit != 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................F
=================================== FAILURES ===================================
_ TestRateLimitLabelPruning.test_prune_removes_labels_for_evicted_installations _

self = <tests.test_github_client.TestRateLimitLabelPruning object at 0x72b89d5865d0>

    def test_prune_removes_labels_for_evicted_installations(self):
        """Test gauge label sets follow the credential cache, so churn cannot grow them."""
        from stampbot.github_client import GitHubAppClient
        from stampbot.metrics import github_api_rate_limit_limit, github_api_rate_limit_remaining
    
        client = GitHubAppClient()
        for installation_id in (9001, 9002):
            fake = Mock()
            fake.requester.rate_limiting = (10, 5000)
            client._update_rate_limit_metrics(fake, installation_id)
>       assert client._rate_limit_installations == {"9001", "9002"}
E       AssertionError: assert set() == {'9001', '9002'}
E         
E         Extra items in the right set:
E         '9001'
E         '9002'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + set()
E         - {
E         -     '9001',
E         -     '9002',
E         - }

tests/test_github_client.py:519: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRateLimitLabelPruning::test_prune_removes_labels_for_evicted_installations - AssertionError: assert set() == {'9001', '9002'}
  
  Extra items in the right set:
  '9001'
  '9002'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + set()
  - {
  -     '9001',
  -     '9002',
  - }
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 128 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceComparisonOperator_Lt_LtE, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if len(pem_lines) <= 3 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7623edb0fad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7623ea2b49e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) <= 3 or not pem_lines[0].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceComparisonOperator_Lt_LtE, occurrence: 1, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining <= 0 or limit < 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.30s
operator: core/ReplaceComparisonOperator_Lt_LtE, occurrence: 2, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining < 0 or limit <= 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Lt_Gt, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if len(pem_lines) > 3 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.42s
operator: core/ReplaceComparisonOperator_Lt_Gt, occurrence: 1, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining > 0 or limit < 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................F
=================================== FAILURES ===================================
_ TestRateLimitLabelPruning.test_prune_removes_labels_for_evicted_installations _

self = <tests.test_github_client.TestRateLimitLabelPruning object at 0x7b772014e5d0>

    def test_prune_removes_labels_for_evicted_installations(self):
        """Test gauge label sets follow the credential cache, so churn cannot grow them."""
        from stampbot.github_client import GitHubAppClient
        from stampbot.metrics import github_api_rate_limit_limit, github_api_rate_limit_remaining
    
        client = GitHubAppClient()
        for installation_id in (9001, 9002):
            fake = Mock()
            fake.requester.rate_limiting = (10, 5000)
            client._update_rate_limit_metrics(fake, installation_id)
>       assert client._rate_limit_installations == {"9001", "9002"}
E       AssertionError: assert set() == {'9001', '9002'}
E         
E         Extra items in the right set:
E         '9001'
E         '9002'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + set()
E         - {
E         -     '9001',
E         -     '9002',
E         - }

tests/test_github_client.py:519: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRateLimitLabelPruning::test_prune_removes_labels_for_evicted_installations - AssertionError: assert set() == {'9001', '9002'}
  
  Extra items in the right set:
  '9001'
  '9002'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + set()
  - {
  -     '9001',
  -     '9002',
  - }
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 128 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceComparisonOperator_Lt_Gt, occurrence: 2, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining < 0 or limit > 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................F
=================================== FAILURES ===================================
_ TestRateLimitLabelPruning.test_prune_removes_labels_for_evicted_installations _

self = <tests.test_github_client.TestRateLimitLabelPruning object at 0x7da751f3e5d0>

    def test_prune_removes_labels_for_evicted_installations(self):
        """Test gauge label sets follow the credential cache, so churn cannot grow them."""
        from stampbot.github_client import GitHubAppClient
        from stampbot.metrics import github_api_rate_limit_limit, github_api_rate_limit_remaining
    
        client = GitHubAppClient()
        for installation_id in (9001, 9002):
            fake = Mock()
            fake.requester.rate_limiting = (10, 5000)
            client._update_rate_limit_metrics(fake, installation_id)
>       assert client._rate_limit_installations == {"9001", "9002"}
E       AssertionError: assert set() == {'9001', '9002'}
E         
E         Extra items in the right set:
E         '9002'
E         '9001'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + set()
E         - {
E         -     '9001',
E         -     '9002',
E         - }

tests/test_github_client.py:519: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRateLimitLabelPruning::test_prune_removes_labels_for_evicted_installations - AssertionError: assert set() == {'9001', '9002'}
  
  Extra items in the right set:
  '9002'
  '9001'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + set()
  - {
  -     '9001',
  -     '9002',
  - }
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 128 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceComparisonOperator_Lt_GtE, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if len(pem_lines) >= 3 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x745aa4f93ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x745aa1ce89e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) >= 3 or not pem_lines[0].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceComparisonOperator_Lt_GtE, occurrence: 1, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining >= 0 or limit < 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................F
=================================== FAILURES ===================================
_ TestRateLimitLabelPruning.test_prune_removes_labels_for_evicted_installations _

self = <tests.test_github_client.TestRateLimitLabelPruning object at 0x7c975319a5d0>

    def test_prune_removes_labels_for_evicted_installations(self):
        """Test gauge label sets follow the credential cache, so churn cannot grow them."""
        from stampbot.github_client import GitHubAppClient
        from stampbot.metrics import github_api_rate_limit_limit, github_api_rate_limit_remaining
    
        client = GitHubAppClient()
        for installation_id in (9001, 9002):
            fake = Mock()
            fake.requester.rate_limiting = (10, 5000)
            client._update_rate_limit_metrics(fake, installation_id)
>       assert client._rate_limit_installations == {"9001", "9002"}
E       AssertionError: assert set() == {'9001', '9002'}
E         
E         Extra items in the right set:
E         '9001'
E         '9002'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + set()
E         - {
E         -     '9001',
E         -     '9002',
E         - }

tests/test_github_client.py:519: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRateLimitLabelPruning::test_prune_removes_labels_for_evicted_installations - AssertionError: assert set() == {'9001', '9002'}
  
  Extra items in the right set:
  '9001'
  '9002'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + set()
  - {
  -     '9001',
  -     '9002',
  - }
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 128 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceComparisonOperator_Lt_GtE, occurrence: 2, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining < 0 or limit >= 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................F
=================================== FAILURES ===================================
_ TestRateLimitLabelPruning.test_prune_removes_labels_for_evicted_installations _

self = <tests.test_github_client.TestRateLimitLabelPruning object at 0x7480b765e5d0>

    def test_prune_removes_labels_for_evicted_installations(self):
        """Test gauge label sets follow the credential cache, so churn cannot grow them."""
        from stampbot.github_client import GitHubAppClient
        from stampbot.metrics import github_api_rate_limit_limit, github_api_rate_limit_remaining
    
        client = GitHubAppClient()
        for installation_id in (9001, 9002):
            fake = Mock()
            fake.requester.rate_limiting = (10, 5000)
            client._update_rate_limit_metrics(fake, installation_id)
>       assert client._rate_limit_installations == {"9001", "9002"}
E       AssertionError: assert set() == {'9001', '9002'}
E         
E         Extra items in the right set:
E         '9002'
E         '9001'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + set()
E         - {
E         -     '9001',
E         -     '9002',
E         - }

tests/test_github_client.py:519: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRateLimitLabelPruning::test_prune_removes_labels_for_evicted_installations - AssertionError: assert set() == {'9001', '9002'}
  
  Extra items in the right set:
  '9002'
  '9001'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + set()
  - {
  -     '9001',
  -     '9002',
  - }
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 128 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -236,7 +236,7 @@
                     file_metadata = os.fstat(key_file.fileno())
                     if not stat.S_ISREG(file_metadata.st_mode):
                         raise ValueError("Private key path must reference a regular file")
-                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
+                    if file_metadata.st_size == MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                     pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
             except FileNotFoundError:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -245,7 +245,7 @@
                 logger.error("Failed to read private key file: %s", e)
                 raise
 
-        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
+        if len(pem_content.encode("utf-8")) == MAX_PRIVATE_KEY_SIZE:
             raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
 
         # Validate a complete PEM private-key envelope. PyGithub performs the
........................................................................ [ 15%]
...............................................F
=================================== FAILURES ===================================
___ TestLoadPrivateKey.test_load_private_key_rejects_oversized_inline_value ____

self = <tests.test_github_client.TestLoadPrivateKey object at 0x733c9b02f110>

    def test_load_private_key_rejects_oversized_inline_value(self):
        """Test an oversized inline value is rejected before PEM parsing."""
        from stampbot.github_client import MAX_PRIVATE_KEY_SIZE, GitHubAppClient
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = TEST_PEM_KEY.replace("test", "x" * MAX_PRIVATE_KEY_SIZE)
    
>           with pytest.raises(ValueError, match="exceeds"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE ValueError

tests/test_github_client.py:281: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_rejects_oversized_inline_value - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 119 passed, 3 deselected, 1 warning in 1.38s
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 2, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if self._bot_login_value is not None and self._bot_login_value[1] == now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x70fe4727b4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
            assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
    
            # The App slug is read once and reused for later review scans, and
            # each operation builds exactly one client on the shared credentials.
            client.find_bot_approval_reviews(123456, "owner/repo", 43)
>           mock_integration.get_app.assert_called_once_with()

tests/test_github_client.py:1489: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='GithubIntegration().get_app' id='124237388324816'>, args = ()
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_app' to be called once. Called 2 times.
E           Calls: [call(), call()].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: Expected 'get_app' to be called once. Called 2 times.
Calls: [call(), call()].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -236,7 +236,7 @@
                     file_metadata = os.fstat(key_file.fileno())
                     if not stat.S_ISREG(file_metadata.st_mode):
                         raise ValueError("Private key path must reference a regular file")
-                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
+                    if file_metadata.st_size != MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                     pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
             except FileNotFoundError:
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x73169fff0d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-907/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x73169c810320>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size != MAX_PRIVATE_KEY_SIZE:
>                       raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E                       ValueError: Private key file exceeds 65536 bytes

stampbot/github_client.py:240: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - ValueError: Private key file exceeds 65536 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.36s
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -245,7 +245,7 @@
                 logger.error("Failed to read private key file: %s", e)
                 raise
 
-        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
+        if len(pem_content.encode("utf-8")) != MAX_PRIVATE_KEY_SIZE:
             raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
 
         # Validate a complete PEM private-key envelope. PyGithub performs the
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7480acbfbad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7480a98f89e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) != MAX_PRIVATE_KEY_SIZE:
>           raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E           ValueError: Private key exceeds 65536 bytes

stampbot/github_client.py:249: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key exceeds 65536 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.37s
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 2, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if self._bot_login_value is not None and self._bot_login_value[1] != now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -236,7 +236,7 @@
                     file_metadata = os.fstat(key_file.fileno())
                     if not stat.S_ISREG(file_metadata.st_mode):
                         raise ValueError("Private key path must reference a regular file")
-                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
+                    if file_metadata.st_size < MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                     pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
             except FileNotFoundError:
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x72d053150d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-1513/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x72d04bc08320>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size < MAX_PRIVATE_KEY_SIZE:
>                       raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E                       ValueError: Private key file exceeds 65536 bytes

stampbot/github_client.py:240: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - ValueError: Private key file exceeds 65536 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -245,7 +245,7 @@
                 logger.error("Failed to read private key file: %s", e)
                 raise
 
-        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
+        if len(pem_content.encode("utf-8")) < MAX_PRIVATE_KEY_SIZE:
             raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
 
         # Validate a complete PEM private-key envelope. PyGithub performs the
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x78b0c4fffad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x78b0c1ddc9e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) < MAX_PRIVATE_KEY_SIZE:
>           raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E           ValueError: Private key exceeds 65536 bytes

stampbot/github_client.py:249: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key exceeds 65536 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 2, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if self._bot_login_value is not None and self._bot_login_value[1] < now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x70d358d8b4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
            assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
    
            # The App slug is read once and reused for later review scans, and
            # each operation builds exactly one client on the shared credentials.
            client.find_bot_approval_reviews(123456, "owner/repo", 43)
>           mock_integration.get_app.assert_called_once_with()

tests/test_github_client.py:1489: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='GithubIntegration().get_app' id='124053001510864'>, args = ()
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_app' to be called once. Called 2 times.
E           Calls: [call(), call()].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: Expected 'get_app' to be called once. Called 2 times.
Calls: [call(), call()].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -236,7 +236,7 @@
                     file_metadata = os.fstat(key_file.fileno())
                     if not stat.S_ISREG(file_metadata.st_mode):
                         raise ValueError("Private key path must reference a regular file")
-                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
+                    if file_metadata.st_size <= MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                     pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
             except FileNotFoundError:
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x7fb62d548d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-632/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7fb62a110320>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size <= MAX_PRIVATE_KEY_SIZE:
>                       raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E                       ValueError: Private key file exceeds 65536 bytes

stampbot/github_client.py:240: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - ValueError: Private key file exceeds 65536 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -245,7 +245,7 @@
                 logger.error("Failed to read private key file: %s", e)
                 raise
 
-        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
+        if len(pem_content.encode("utf-8")) <= MAX_PRIVATE_KEY_SIZE:
             raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
 
         # Validate a complete PEM private-key envelope. PyGithub performs the
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7a1ad89bfad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7a1ad51889e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) <= MAX_PRIVATE_KEY_SIZE:
>           raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E           ValueError: Private key exceeds 65536 bytes

stampbot/github_client.py:249: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key exceeds 65536 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 2, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if self._bot_login_value is not None and self._bot_login_value[1] <= now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x793a460974d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
            assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
    
            # The App slug is read once and reused for later review scans, and
            # each operation builds exactly one client on the shared credentials.
            client.find_bot_approval_reviews(123456, "owner/repo", 43)
>           mock_integration.get_app.assert_called_once_with()

tests/test_github_client.py:1489: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='GithubIntegration().get_app' id='133291160674256'>, args = ()
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_app' to be called once. Called 2 times.
E           Calls: [call(), call()].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: Expected 'get_app' to be called once. Called 2 times.
Calls: [call(), call()].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -236,7 +236,7 @@
                     file_metadata = os.fstat(key_file.fileno())
                     if not stat.S_ISREG(file_metadata.st_mode):
                         raise ValueError("Private key path must reference a regular file")
-                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
+                    if file_metadata.st_size >= MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                     pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
             except FileNotFoundError:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -245,7 +245,7 @@
                 logger.error("Failed to read private key file: %s", e)
                 raise
 
-        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
+        if len(pem_content.encode("utf-8")) >= MAX_PRIVATE_KEY_SIZE:
             raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
 
         # Validate a complete PEM private-key envelope. PyGithub performs the
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 2, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if self._bot_login_value is not None and self._bot_login_value[1] >= now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -236,7 +236,7 @@
                     file_metadata = os.fstat(key_file.fileno())
                     if not stat.S_ISREG(file_metadata.st_mode):
                         raise ValueError("Private key path must reference a regular file")
-                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
+                    if file_metadata.st_size is MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                     pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
             except FileNotFoundError:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -245,7 +245,7 @@
                 logger.error("Failed to read private key file: %s", e)
                 raise
 
-        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
+        if len(pem_content.encode("utf-8")) is MAX_PRIVATE_KEY_SIZE:
             raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
 
         # Validate a complete PEM private-key envelope. PyGithub performs the
........................................................................ [ 15%]
...............................................F
=================================== FAILURES ===================================
___ TestLoadPrivateKey.test_load_private_key_rejects_oversized_inline_value ____

self = <tests.test_github_client.TestLoadPrivateKey object at 0x774b06e37110>

    def test_load_private_key_rejects_oversized_inline_value(self):
        """Test an oversized inline value is rejected before PEM parsing."""
        from stampbot.github_client import MAX_PRIVATE_KEY_SIZE, GitHubAppClient
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = TEST_PEM_KEY.replace("test", "x" * MAX_PRIVATE_KEY_SIZE)
    
>           with pytest.raises(ValueError, match="exceeds"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE ValueError

tests/test_github_client.py:281: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_rejects_oversized_inline_value - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 119 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 2, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if self._bot_login_value is not None and self._bot_login_value[1] is now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7ba2803274d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
            assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
    
            # The App slug is read once and reused for later review scans, and
            # each operation builds exactly one client on the shared credentials.
            client.find_bot_approval_reviews(123456, "owner/repo", 43)
>           mock_integration.get_app.assert_called_once_with()

tests/test_github_client.py:1489: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='GithubIntegration().get_app' id='135937836638160'>, args = ()
kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'get_app' to be called once. Called 2 times.
E           Calls: [call(), call()].

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: Expected 'get_app' to be called once. Called 2 times.
Calls: [call(), call()].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -236,7 +236,7 @@
                     file_metadata = os.fstat(key_file.fileno())
                     if not stat.S_ISREG(file_metadata.st_mode):
                         raise ValueError("Private key path must reference a regular file")
-                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
+                    if file_metadata.st_size is not MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                     pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
             except FileNotFoundError:
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x701f17044d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-984/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x701f0fc7c320>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size is not MAX_PRIVATE_KEY_SIZE:
>                       raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E                       ValueError: Private key file exceeds 65536 bytes

stampbot/github_client.py:240: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - ValueError: Private key file exceeds 65536 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -245,7 +245,7 @@
                 logger.error("Failed to read private key file: %s", e)
                 raise
 
-        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
+        if len(pem_content.encode("utf-8")) is not MAX_PRIVATE_KEY_SIZE:
             raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
 
         # Validate a complete PEM private-key envelope. PyGithub performs the
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x71670140fad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7166fe0e49e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) is not MAX_PRIVATE_KEY_SIZE:
>           raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E           ValueError: Private key exceeds 65536 bytes

stampbot/github_client.py:249: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key exceeds 65536 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 2, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if self._bot_login_value is not None and self._bot_login_value[1] is not now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_GtE_Eq, occurrence: 0, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1149,7 +1149,7 @@
                     permission_index = -1
                     required_index = 99
 
-                has_permission = permission_index >= required_index
+                has_permission = permission_index == required_index
 
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceComparisonOperator_GtE_NotEq, occurrence: 0, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1149,7 +1149,7 @@
                     permission_index = -1
                     required_index = 99
 
-                has_permission = permission_index >= required_index
+                has_permission = permission_index != required_index
 
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
........................................................................ [ 15%]
........................................................................ [ 30%]
................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________

self = <tests.test_github_client.TestUserHasPermission object at 0x7cc51138b750>

    def test_user_has_permission_true(self):
        """Test required permission satisfied returns True."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.return_value = "write"
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "alice", "write")
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:1566: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 160 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceComparisonOperator_GtE_Lt, occurrence: 0, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1149,7 +1149,7 @@
                     permission_index = -1
                     required_index = 99
 
-                has_permission = permission_index >= required_index
+                has_permission = permission_index < required_index
 
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
........................................................................ [ 15%]
........................................................................ [ 30%]
................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________

self = <tests.test_github_client.TestUserHasPermission object at 0x712c2238f750>

    def test_user_has_permission_true(self):
        """Test required permission satisfied returns True."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.return_value = "write"
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "alice", "write")
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:1566: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 160 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceComparisonOperator_GtE_LtE, occurrence: 0, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1149,7 +1149,7 @@
                     permission_index = -1
                     required_index = 99
 
-                has_permission = permission_index >= required_index
+                has_permission = permission_index <= required_index
 
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
........................................................................ [ 15%]
........................................................................ [ 30%]
.................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_false _____________

self = <tests.test_github_client.TestUserHasPermission object at 0x7eaded98b890>

    def test_user_has_permission_false(self):
        """Test insufficient permission returns False."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.return_value = "read"
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "bob", "write")
    
>           assert result is False
E           assert True is False

tests/test_github_client.py:1603: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_false - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 161 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceComparisonOperator_GtE_Gt, occurrence: 0, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1149,7 +1149,7 @@
                     permission_index = -1
                     required_index = 99
 
-                has_permission = permission_index >= required_index
+                has_permission = permission_index > required_index
 
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
........................................................................ [ 15%]
........................................................................ [ 30%]
................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________

self = <tests.test_github_client.TestUserHasPermission object at 0x7ddd61f87750>

    def test_user_has_permission_true(self):
        """Test required permission satisfied returns True."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.return_value = "write"
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "alice", "write")
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:1566: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 160 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceComparisonOperator_GtE_Is, occurrence: 0, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1149,7 +1149,7 @@
                     permission_index = -1
                     required_index = 99
 
-                has_permission = permission_index >= required_index
+                has_permission = permission_index is required_index
 
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_GtE_IsNot, occurrence: 0, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1149,7 +1149,7 @@
                     permission_index = -1
                     required_index = 99
 
-                has_permission = permission_index >= required_index
+                has_permission = permission_index is not required_index
 
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
........................................................................ [ 15%]
........................................................................ [ 30%]
................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_true ______________

self = <tests.test_github_client.TestUserHasPermission object at 0x706b84b57750>

    def test_user_has_permission_true(self):
        """Test required permission satisfied returns True."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.return_value = "write"
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "alice", "write")
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:1566: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 160 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 0, definition_name: _get_repo_contents
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -78,7 +78,7 @@
     ref: str | None,
 ) -> list[ContentFile] | ContentFile:
     """Call PyGithub without passing ``None`` as an explicit Git ref."""
-    if ref is None:
+    if ref is not None:
         return repo.get_contents(path)
     return repo.get_contents(path, ref=ref)
 
........................................................................ [ 15%]
......................................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________

self = <tests.test_github_client.TestGetRepoFile object at 0x7b681586b390>

    def test_get_repo_file_success(self):
        """Test successful file retrieval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_content = Mock()
            mock_content.decoded_content = b"file content"
            mock_repo = Mock()
            mock_repo.get_contents.return_value = mock_content
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_repo_file(
                123456,
                "owner/repo",
                "stampbot.toml",
                ref="main",
            )
    
            assert result == "file content"
>           mock_repo.get_contents.assert_called_once_with("stampbot.toml", ref="main")

tests/test_github_client.py:986: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='135686912488656'>
args = ('stampbot.toml',), kwargs = {'ref': 'main'}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: get_contents('stampbot.toml', ref='main')
E             Actual: get_contents('stampbot.toml')

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - AssertionError: expected call not found.
Expected: get_contents('stampbot.toml', ref='main')
  Actual: get_contents('stampbot.toml')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 1, definition_name: _ensure_initialized
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -182,7 +182,7 @@
 
             # is_configured() guarantees app_id is set, but check for type safety
             app_id = settings.app_id
-            if app_id is None:
+            if app_id is not None:
                 raise RuntimeError("App ID not configured")
 
             private_key = self._load_private_key()
........................................................................ [ 15%]
..................................F
=================================== FAILURES ===================================
____ TestEnsureInitialized.test_ensure_initialized_raises_when_app_id_none _____

self = <tests.test_github_client.TestEnsureInitialized object at 0x7837b1d54c30>

    def test_ensure_initialized_raises_when_app_id_none(self):
        """Test that _ensure_initialized raises when app_id is None."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
        ):
            mock_settings.app_id = None
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(RuntimeError, match="App ID not configured"):
>               client._ensure_initialized()

tests/test_github_client.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7837ae9f2f90>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_raises_when_app_id_none - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 106 passed, 3 deselected, 1 warning in 1.38s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 2, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -221,7 +221,7 @@
         """
         key: str | None = settings.private_key
 
-        if key is None:
+        if key is not None:
             raise RuntimeError("Private key not configured")
 
         # A non-PEM value is an operator-selected file path. It is not
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x77750e6cfad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x77750b43c9e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is not None:
>           raise RuntimeError("Private key not configured")
E           RuntimeError: Private key not configured

stampbot/github_client.py:225: RuntimeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - RuntimeError: Private key not configured
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 3, definition_name: _installation_auth
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -303,7 +303,7 @@
         """
         with self._installation_auths_lock:
             installation_auth = self._installation_auths.get(installation_id)
-            if installation_auth is None:
+            if installation_auth is not None:
                 # The App credentials must be loaded before an installation auth
                 # can be derived from them.
                 self._ensure_initialized()
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x72eac138e350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='126352532019184'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x72eabe079090>
installation_id = 123456

    def _get_installation_client(self, installation_id: int) -> Github:
        """Get an authenticated GitHub client for an installation.
    
        Every call builds a private lazy client on credentials shared per
        installation, so the token is reused while connection state stays
        private to the calling thread. Binding the client gives the credentials
        the requester PyGithub needs for an exchange. The token is then read
        here, before any repository request, so the first exchange or a due
        refresh happens now and an authentication failure never looks like a
        missing repository. A failed exchange leaves the credentials in place;
        the next read retries.
    
        Args:
            installation_id: GitHub App installation ID.
    
        Returns:
            Authenticated Github client instance with timeout and retry configured.
    
        Raises:
            github.GithubException: If token exchange fails.
        """
        installation_auth = self._installation_auth(installation_id)
        client = self._new_client(installation_auth)
>       installation_auth.token  # noqa: B018
        ^^^^^^^^^^^^^^^^^^^^^^^
E       AttributeError: 'NoneType' object has no attribute 'token'

stampbot/github_client.py:354: AttributeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - AttributeError: 'NoneType' object has no attribute 'token'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 0, definition_name: integration
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -205,7 +205,7 @@
             RuntimeError: If GitHub App credentials are not configured.
         """
         self._ensure_initialized()
-        assert self._integration is not None  # guaranteed by _ensure_initialized
+        assert self._integration is None  # guaranteed by _ensure_initialized
         return self._integration
 
     def _load_private_key(self) -> str:
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7b38a1d9b4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:08:35.707953Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo:  [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': ''}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': ''}, 'event': 'Failed to find bot approval reviews for PR #42 in owner/repo: ', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:08:35.707953Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 1, definition_name: _installation_auth
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -307,7 +307,7 @@
                 # The App credentials must be loaded before an installation auth
                 # can be derived from them.
                 self._ensure_initialized()
-                assert self._auth is not None  # guaranteed by _ensure_initialized
+                assert self._auth is None  # guaranteed by _ensure_initialized
                 installation_auth = InstrumentedInstallationAuth(self._auth, installation_id)
                 self._installation_auths[installation_id] = installation_auth
                 self._prune_rate_limit_metrics()
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x7e89f0d82350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='139130858625008'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:352: in _get_installation_client
    installation_auth = self._installation_auth(installation_id)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e89eda49130>
installation_id = 123456

    def _installation_auth(self, installation_id: int) -> InstrumentedInstallationAuth:
        """Return the shared credentials for an installation, creating them on first use.
    
        No network request happens here, so the cache lock is held only briefly.
        The token exchange happens when a caller reads ``token``, serialized per
        installation by the credentials themselves.
    
        Args:
            installation_id: GitHub App installation ID.
    
        Returns:
            Credentials whose token PyGithub exchanges on first read and
            refreshes before GitHub expires it.
    
        Raises:
            RuntimeError: If the GitHub App credentials are not configured.
        """
        with self._installation_auths_lock:
            installation_auth = self._installation_auths.get(installation_id)
            if installation_auth is None:
                # The App credentials must be loaded before an installation auth
                # can be derived from them.
                self._ensure_initialized()
>               assert self._auth is None  # guaranteed by _ensure_initialized
                       ^^^^^^^^^^^^^^^^^^
E               AssertionError

stampbot/github_client.py:310: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 2, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if self._bot_login_value is None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7d4e0d27b4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:27:42.114538Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: 'NoneType' object is not subscriptable [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "'NoneType' object is not subscriptable"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "'NoneType' object is not subscriptable"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: 'NoneType' object is not subscriptable", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:27:42.114538Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 3, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -706,7 +706,7 @@
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
-                if e.status == 404 and repo is not None:
+                if e.status == 404 and repo is None:
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x7e0a23c315b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='138581958898560'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:21:56.747075Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:21:56.747075Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceUnaryOperator_USub_UAdd, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[+1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x76edbb0cfad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x76edb37a49e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[+1] != expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceUnaryOperator_USub_UAdd, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -258,7 +258,7 @@
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
-        if not any(line.strip() for line in pem_lines[1:-1]):
+        if not any(line.strip() for line in pem_lines[1:+1]):
             raise ValueError("Private key PEM body is empty")
 
         return pem_content
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7744273efad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7744241509e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] != expected_footer:
            raise ValueError("Private key PEM footer does not match its header")
        if not any(line.strip() for line in pem_lines[1:+1]):
>           raise ValueError("Private key PEM body is empty")
E           ValueError: Private key PEM body is empty

stampbot/github_client.py:262: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM body is empty
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceUnaryOperator_USub_UAdd, occurrence: 2, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1146,7 +1146,7 @@
                     permission_index = permission_order.index(permission)
                     required_index = permission_order.index(required_permission)
                 except ValueError:
-                    permission_index = -1
+                    permission_index = +1
                     required_index = 99
 
                 has_permission = permission_index >= required_index
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceUnaryOperator_USub_UAdd, occurrence: 3, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1231,7 +1231,7 @@
 
                 for team_ref in allowed_teams:
                     # Extract team slug (handle both "org/team" and "team" formats)
-                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+                    team_slug = team_ref.split("/")[+1] if "/" in team_ref else team_ref
 
                     try:
                         team = org.get_team_by_slug(team_slug)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceUnaryOperator_USub_Invert, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[~1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7c21fe2c7ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7c21faaa49e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[~1] != expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceUnaryOperator_USub_Invert, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -258,7 +258,7 @@
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
-        if not any(line.strip() for line in pem_lines[1:-1]):
+        if not any(line.strip() for line in pem_lines[1:~1]):
             raise ValueError("Private key PEM body is empty")
 
         return pem_content
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x79c6c31d3ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x79c6bbdf09e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] != expected_footer:
            raise ValueError("Private key PEM footer does not match its header")
        if not any(line.strip() for line in pem_lines[1:~1]):
>           raise ValueError("Private key PEM body is empty")
E           ValueError: Private key PEM body is empty

stampbot/github_client.py:262: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM body is empty
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceUnaryOperator_USub_Invert, occurrence: 2, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1146,7 +1146,7 @@
                     permission_index = permission_order.index(permission)
                     required_index = permission_order.index(required_permission)
                 except ValueError:
-                    permission_index = -1
+                    permission_index = ~1
                     required_index = 99
 
                 has_permission = permission_index >= required_index
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceUnaryOperator_USub_Invert, occurrence: 3, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1231,7 +1231,7 @@
 
                 for team_ref in allowed_teams:
                     # Extract team slug (handle both "org/team" and "team" formats)
-                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+                    team_slug = team_ref.split("/")[~1] if "/" in team_ref else team_ref
 
                     try:
                         team = org.get_team_by_slug(team_slug)
........................................................................ [ 15%]
........................................................................ [ 30%]
.....................F
=================================== FAILURES ===================================
________ TestGetUserTeamSlugs.test_get_user_team_slugs_with_org_prefix _________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7bdc01d77b10>

    def test_get_user_team_slugs_with_org_prefix(self):
        """Test team membership check with org/team format."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_user = Mock()
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(123456, "acme", "alice", ["acme/release-team"])
    
            # Should extract "release-team" from "acme/release-team"
>           mock_org.get_team_by_slug.assert_called_with("release-team")

tests/test_github_client.py:1775: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization().get_team_by_slug' id='136184823550640'>
args = ('release-team',), kwargs = {}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: get_team_by_slug('release-team')
E             Actual: get_team_by_slug('acme')

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_with_org_prefix - AssertionError: expected call not found.
Expected: get_team_by_slug('release-team')
  Actual: get_team_by_slug('acme')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 165 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceUnaryOperator_USub_Not, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[not 1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x74d390183ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x74d38ced49e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[not 1] != expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceUnaryOperator_USub_Not, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -258,7 +258,7 @@
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
-        if not any(line.strip() for line in pem_lines[1:-1]):
+        if not any(line.strip() for line in pem_lines[1:not 1]):
             raise ValueError("Private key PEM body is empty")
 
         return pem_content
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x723603c07ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7236003b09e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] != expected_footer:
            raise ValueError("Private key PEM footer does not match its header")
        if not any(line.strip() for line in pem_lines[1:not 1]):
>           raise ValueError("Private key PEM body is empty")
E           ValueError: Private key PEM body is empty

stampbot/github_client.py:262: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM body is empty
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceUnaryOperator_USub_Not, occurrence: 2, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1146,7 +1146,7 @@
                     permission_index = permission_order.index(permission)
                     required_index = permission_order.index(required_permission)
                 except ValueError:
-                    permission_index = -1
+                    permission_index = not 1
                     required_index = 99
 
                 has_permission = permission_index >= required_index
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceUnaryOperator_USub_Not, occurrence: 3, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1231,7 +1231,7 @@
 
                 for team_ref in allowed_teams:
                     # Extract team slug (handle both "org/team" and "team" formats)
-                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+                    team_slug = team_ref.split("/")[not 1] if "/" in team_ref else team_ref
 
                     try:
                         team = org.get_team_by_slug(team_slug)
........................................................................ [ 15%]
........................................................................ [ 30%]
.....................F
=================================== FAILURES ===================================
________ TestGetUserTeamSlugs.test_get_user_team_slugs_with_org_prefix _________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7c4bc4183b10>

    def test_get_user_team_slugs_with_org_prefix(self):
        """Test team membership check with org/team format."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_user = Mock()
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(123456, "acme", "alice", ["acme/release-team"])
    
            # Should extract "release-team" from "acme/release-team"
>           mock_org.get_team_by_slug.assert_called_with("release-team")

tests/test_github_client.py:1775: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization().get_team_by_slug' id='136664823878320'>
args = ('release-team',), kwargs = {}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: get_team_by_slug('release-team')
E             Actual: get_team_by_slug('acme')

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_with_org_prefix - AssertionError: expected call not found.
Expected: get_team_by_slug('release-team')
  Actual: get_team_by_slug('acme')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 165 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceUnaryOperator_Delete_USub, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7254c64dfad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7254c2c989e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[1] != expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceUnaryOperator_Delete_USub, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -258,7 +258,7 @@
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
-        if not any(line.strip() for line in pem_lines[1:-1]):
+        if not any(line.strip() for line in pem_lines[1:1]):
             raise ValueError("Private key PEM body is empty")
 
         return pem_content
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x724defadbad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x724dec8389e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] != expected_footer:
            raise ValueError("Private key PEM footer does not match its header")
        if not any(line.strip() for line in pem_lines[1:1]):
>           raise ValueError("Private key PEM body is empty")
E           ValueError: Private key PEM body is empty

stampbot/github_client.py:262: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM body is empty
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceUnaryOperator_Delete_USub, occurrence: 2, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1146,7 +1146,7 @@
                     permission_index = permission_order.index(permission)
                     required_index = permission_order.index(required_permission)
                 except ValueError:
-                    permission_index = -1
+                    permission_index = 1
                     required_index = 99
 
                 has_permission = permission_index >= required_index
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceUnaryOperator_Delete_USub, occurrence: 3, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1231,7 +1231,7 @@
 
                 for team_ref in allowed_teams:
                     # Extract team slug (handle both "org/team" and "team" formats)
-                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+                    team_slug = team_ref.split("/")[1] if "/" in team_ref else team_ref
 
                     try:
                         team = org.get_team_by_slug(team_slug)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0, definition_name: _ensure_initialized
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -175,7 +175,7 @@
             if self._initialized:
                 return
 
-            if not is_configured():
+            if  is_configured():
                 raise RuntimeError(
                     "GitHub App not configured. Visit /setup to create your GitHub App."
                 )
........................................................................ [ 15%]
.................................F
=================================== FAILURES ===================================
___ TestEnsureInitialized.test_ensure_initialized_raises_when_not_configured ___

self = <tests.test_github_client.TestEnsureInitialized object at 0x791ad4b34b00>

    def test_ensure_initialized_raises_when_not_configured(self):
        """Test that _ensure_initialized raises when app not configured."""
        with patch("stampbot.github_client.is_configured", return_value=False):
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(RuntimeError, match="GitHub App not configured"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE RuntimeError

tests/test_github_client.py:83: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_raises_when_not_configured - Failed: DID NOT RAISE RuntimeError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 105 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -234,7 +234,7 @@
             try:
                 with Path(key).expanduser().open(encoding="utf-8") as key_file:
                     file_metadata = os.fstat(key_file.fileno())
-                    if not stat.S_ISREG(file_metadata.st_mode):
+                    if  stat.S_ISREG(file_metadata.st_mode):
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x769fa6458d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-1452/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x769fa2cb4410>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if  stat.S_ISREG(file_metadata.st_mode):
>                       raise ValueError("Private key path must reference a regular file")
E                       ValueError: Private key path must reference a regular file

stampbot/github_client.py:238: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - ValueError: Private key path must reference a regular file
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if len(pem_lines) < 3 or  pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7a39be2efad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7a39bb0dc9e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or  pem_lines[0].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 3, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -253,7 +253,7 @@
         pem_lines = pem_content.strip().splitlines()
         if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
-        if not pem_lines[0].endswith("PRIVATE KEY-----"):
+        if  pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7ce0819e7ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7ce07e7f09e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if  pem_lines[0].endswith("PRIVATE KEY-----"):
>           raise ValueError("PEM value must contain a private key")
E           ValueError: PEM value must contain a private key

stampbot/github_client.py:257: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: PEM value must contain a private key
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.40s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 4, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -258,7 +258,7 @@
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
-        if not any(line.strip() for line in pem_lines[1:-1]):
+        if  any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
 
         return pem_content
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7c3b371e7ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7c3b2fe3c9e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] != expected_footer:
            raise ValueError("Private key PEM footer does not match its header")
        if  any(line.strip() for line in pem_lines[1:-1]):
>           raise ValueError("Private key PEM body is empty")
E           ValueError: Private key PEM body is empty

stampbot/github_client.py:262: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM body is empty
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.41s
operator: core/AddNot, occurrence: 0, definition_name: _get_repo_contents
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -78,7 +78,7 @@
     ref: str | None,
 ) -> list[ContentFile] | ContentFile:
     """Call PyGithub without passing ``None`` as an explicit Git ref."""
-    if ref is None:
+    if not ref is None:
         return repo.get_contents(path)
     return repo.get_contents(path, ref=ref)
 
........................................................................ [ 15%]
......................................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________

self = <tests.test_github_client.TestGetRepoFile object at 0x7f241f927390>

    def test_get_repo_file_success(self):
        """Test successful file retrieval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_content = Mock()
            mock_content.decoded_content = b"file content"
            mock_repo = Mock()
            mock_repo.get_contents.return_value = mock_content
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_repo_file(
                123456,
                "owner/repo",
                "stampbot.toml",
                ref="main",
            )
    
            assert result == "file content"
>           mock_repo.get_contents.assert_called_once_with("stampbot.toml", ref="main")

tests/test_github_client.py:986: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='139793068831952'>
args = ('stampbot.toml',), kwargs = {'ref': 'main'}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: get_contents('stampbot.toml', ref='main')
E             Actual: get_contents('stampbot.toml')

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - AssertionError: expected call not found.
Expected: get_contents('stampbot.toml', ref='main')
  Actual: get_contents('stampbot.toml')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected, 1 warning in 1.49s
operator: core/AddNot, occurrence: 1, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -119,7 +119,7 @@
     ``.github`` repository. A readable repository root proves the file is
     absent. Anything else stays unconfirmed and the caller fails closed.
     """
-    if optional_repo and _repository_is_missing(repo):
+    if not optional_repo and _repository_is_missing(repo):
         return "repository"
     if _can_read_repo_root(repo, ref):
         return "file"
........................................................................ [ 15%]
........................................................................ [ 30%]
.......F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_accepts_missing_optional_repository_with_lazy_client _

self = <tests.test_github_client.TestGetRepoFile object at 0x76bc3df14d70>

    def test_get_repo_file_accepts_missing_optional_repository_with_lazy_client(self):
        """Test a lazy client confirms the optional repository itself is missing."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_integration_cls.return_value = mock_integration
    
            # Lazy get_repo never raises. The file read and the root read both
            # 404, and only the repository request proves the repo is absent.
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                GithubException(404, {"message": "Not Found"}, None),
            ]
            mock_repo.complete.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/.github",
                "stampbot.toml",
                missing_repository_is_optional=True,
            )

tests/test_github_client.py:1236: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='130550809639104'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:20:48.004316Z [warning  ] GitHub could not read stampbot.toml from owner/.github (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/.github (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:20:48.004316Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_accepts_missing_optional_repository_with_lazy_client - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 151 passed, 3 deselected, 1 warning in 1.48s
operator: core/AddNot, occurrence: 2, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -121,7 +121,7 @@
     """
     if optional_repo and _repository_is_missing(repo):
         return "repository"
-    if _can_read_repo_root(repo, ref):
+    if not _can_read_repo_root(repo, ref):
         return "file"
     return "unconfirmed"
 
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x788fba0f55b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='132558641247104'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:14:56.222419Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:14:56.222419Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.46s
operator: core/AddNot, occurrence: 3, definition_name: _ensure_initialized
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -168,7 +168,7 @@
         Raises:
             RuntimeError: If GitHub App credentials are not configured.
         """
-        if self._initialized:
+        if not self._initialized:
             return
 
         with self._init_lock:
........................................................................ [ 15%]
.................................F
=================================== FAILURES ===================================
___ TestEnsureInitialized.test_ensure_initialized_raises_when_not_configured ___

self = <tests.test_github_client.TestEnsureInitialized object at 0x741eaae5cb00>

    def test_ensure_initialized_raises_when_not_configured(self):
        """Test that _ensure_initialized raises when app not configured."""
        with patch("stampbot.github_client.is_configured", return_value=False):
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(RuntimeError, match="GitHub App not configured"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE RuntimeError

tests/test_github_client.py:83: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_raises_when_not_configured - Failed: DID NOT RAISE RuntimeError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 105 passed, 3 deselected, 1 warning in 1.41s
operator: core/AddNot, occurrence: 4, definition_name: _ensure_initialized
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -172,7 +172,7 @@
             return
 
         with self._init_lock:
-            if self._initialized:
+            if not self._initialized:
                 return
 
             if not is_configured():
........................................................................ [ 15%]
................................F
=================================== FAILURES ===================================
______ TestEnsureInitialized.test_ensure_initialized_rechecks_under_lock _______

self = <tests.test_github_client.TestEnsureInitialized object at 0x7363c7279e50>

    def test_ensure_initialized_rechecks_under_lock(self):
        """Test a thread that loses the initialization race does not build credentials again."""
        from stampbot.github_client import GitHubAppClient
    
        client = GitHubAppClient()
    
        class LockThatLosesTheRace:
            def __enter__(self):
                # Another thread finished initializing while this one waited.
                client._initialized = True
    
            def __exit__(self, *exc_info):
                return False
    
        client._init_lock = LockThatLosesTheRace()  # type: ignore[assignment]
        with patch("stampbot.github_client.is_configured") as mock_is_configured:
            client._ensure_initialized()
    
>       mock_is_configured.assert_not_called()

tests/test_github_client.py:74: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='is_configured' id='126872253782672'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'is_configured' to not have been called. Called 1 times.
E           Calls: [call(), call().__bool__()].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_rechecks_under_lock - AssertionError: Expected 'is_configured' to not have been called. Called 1 times.
Calls: [call(), call().__bool__()].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 104 passed, 3 deselected, 1 warning in 1.41s
operator: core/AddNot, occurrence: 5, definition_name: _ensure_initialized
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -175,7 +175,7 @@
             if self._initialized:
                 return
 
-            if not is_configured():
+            if not not is_configured():
                 raise RuntimeError(
                     "GitHub App not configured. Visit /setup to create your GitHub App."
                 )
........................................................................ [ 15%]
.................................F
=================================== FAILURES ===================================
___ TestEnsureInitialized.test_ensure_initialized_raises_when_not_configured ___

self = <tests.test_github_client.TestEnsureInitialized object at 0x7b991da44b00>

    def test_ensure_initialized_raises_when_not_configured(self):
        """Test that _ensure_initialized raises when app not configured."""
        with patch("stampbot.github_client.is_configured", return_value=False):
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(RuntimeError, match="GitHub App not configured"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE RuntimeError

tests/test_github_client.py:83: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_raises_when_not_configured - Failed: DID NOT RAISE RuntimeError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 105 passed, 3 deselected, 1 warning in 1.40s
operator: core/AddNot, occurrence: 6, definition_name: _ensure_initialized
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -182,7 +182,7 @@
 
             # is_configured() guarantees app_id is set, but check for type safety
             app_id = settings.app_id
-            if app_id is None:
+            if not app_id is None:
                 raise RuntimeError("App ID not configured")
 
             private_key = self._load_private_key()
........................................................................ [ 15%]
..................................F
=================================== FAILURES ===================================
____ TestEnsureInitialized.test_ensure_initialized_raises_when_app_id_none _____

self = <tests.test_github_client.TestEnsureInitialized object at 0x7963b785cc30>

    def test_ensure_initialized_raises_when_app_id_none(self):
        """Test that _ensure_initialized raises when app_id is None."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
        ):
            mock_settings.app_id = None
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(RuntimeError, match="App ID not configured"):
>               client._ensure_initialized()

tests/test_github_client.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7963b44f2f90>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_raises_when_app_id_none - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 106 passed, 3 deselected, 1 warning in 1.39s
operator: core/AddNot, occurrence: 7, definition_name: integration
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -205,7 +205,7 @@
             RuntimeError: If GitHub App credentials are not configured.
         """
         self._ensure_initialized()
-        assert self._integration is not None  # guaranteed by _ensure_initialized
+        assert not self._integration is not None  # guaranteed by _ensure_initialized
         return self._integration
 
     def _load_private_key(self) -> str:
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x710f17c974d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:16:00.441885Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo:  [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': ''}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': ''}, 'event': 'Failed to find bot approval reviews for PR #42 in owner/repo: ', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:16:00.441885Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.47s
operator: core/AddNot, occurrence: 8, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -221,7 +221,7 @@
         """
         key: str | None = settings.private_key
 
-        if key is None:
+        if not key is None:
             raise RuntimeError("Private key not configured")
 
         # A non-PEM value is an operator-selected file path. It is not
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x70b37dc07ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x70b37a9449e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if not key is None:
>           raise RuntimeError("Private key not configured")
E           RuntimeError: Private key not configured

stampbot/github_client.py:225: RuntimeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - RuntimeError: Private key not configured
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.41s
operator: core/AddNot, occurrence: 9, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -228,7 +228,7 @@
         # request-controlled, so arbitrary relative and absolute paths are
         # intentional. Validate the opened target instead of pretending that
         # normalizing '..' segments creates a meaningful traversal boundary.
-        if key.startswith("-----BEGIN"):
+        if not key.startswith("-----BEGIN"):
             pem_content = key
         else:
             try:
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7fc03c5f3ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7fc0393e49e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if not key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
>               raise ValueError("Invalid private key path: file does not exist") from None
E               ValueError: Invalid private key path: file does not exist

stampbot/github_client.py:243: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Invalid private key path: file does not exist
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.40s
operator: core/AddNot, occurrence: 10, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -234,7 +234,7 @@
             try:
                 with Path(key).expanduser().open(encoding="utf-8") as key_file:
                     file_metadata = os.fstat(key_file.fileno())
-                    if not stat.S_ISREG(file_metadata.st_mode):
+                    if not not stat.S_ISREG(file_metadata.st_mode):
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x7c5a94928d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-925/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7c5a915b8320>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not not stat.S_ISREG(file_metadata.st_mode):
>                       raise ValueError("Private key path must reference a regular file")
E                       ValueError: Private key path must reference a regular file

stampbot/github_client.py:238: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - ValueError: Private key path must reference a regular file
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.37s
operator: core/AddNot, occurrence: 11, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -236,7 +236,7 @@
                     file_metadata = os.fstat(key_file.fileno())
                     if not stat.S_ISREG(file_metadata.st_mode):
                         raise ValueError("Private key path must reference a regular file")
-                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
+                    if not file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                     pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
             except FileNotFoundError:
........................................................................ [ 15%]
.......................................F
=================================== FAILURES ===================================
___________ TestLoadPrivateKey.test_load_private_key_reads_from_file ___________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x79911f248d60>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-202/test_load_private_key_reads_fr0')

    def test_load_private_key_reads_from_file(self, tmp_path):
        """Test that private key is read from file when path provided."""
        pem_key = TEST_PEM_KEY
        key_file = tmp_path / "private-key.pem"
        key_file.write_text(pem_key)
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._load_private_key()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:172: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x799117874320>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if not file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
>                       raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E                       ValueError: Private key file exceeds 65536 bytes

stampbot/github_client.py:240: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_reads_from_file - ValueError: Private key file exceeds 65536 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 111 passed, 3 deselected, 1 warning in 1.38s
operator: core/AddNot, occurrence: 12, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -245,7 +245,7 @@
                 logger.error("Failed to read private key file: %s", e)
                 raise
 
-        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
+        if not len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
             raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
 
         # Validate a complete PEM private-key envelope. PyGithub performs the
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7ee7077bbad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7ee6ffe989e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if not len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
>           raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
E           ValueError: Private key exceeds 65536 bytes

stampbot/github_client.py:249: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key exceeds 65536 bytes
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.40s
operator: core/AddNot, occurrence: 13, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if not len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7e22d64ffad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e22d32509e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if not len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.41s
operator: core/AddNot, occurrence: 14, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -253,7 +253,7 @@
         pem_lines = pem_content.strip().splitlines()
         if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
-        if not pem_lines[0].endswith("PRIVATE KEY-----"):
+        if not not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7436cffe7ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7436ccd3c9e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not not pem_lines[0].endswith("PRIVATE KEY-----"):
>           raise ValueError("PEM value must contain a private key")
E           ValueError: PEM value must contain a private key

stampbot/github_client.py:257: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: PEM value must contain a private key
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/AddNot, occurrence: 15, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if not pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7e3c04193ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e3c00f409e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if not pem_lines[-1] != expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.40s
operator: core/AddNot, occurrence: 16, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -258,7 +258,7 @@
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
-        if not any(line.strip() for line in pem_lines[1:-1]):
+        if not not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
 
         return pem_content
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x773bf90c7ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x773bf5e509e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] != expected_footer:
            raise ValueError("Private key PEM footer does not match its header")
        if not not any(line.strip() for line in pem_lines[1:-1]):
>           raise ValueError("Private key PEM body is empty")
E           ValueError: Private key PEM body is empty

stampbot/github_client.py:262: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM body is empty
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.40s
operator: core/AddNot, occurrence: 17, definition_name: _installation_auth
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -303,7 +303,7 @@
         """
         with self._installation_auths_lock:
             installation_auth = self._installation_auths.get(installation_id)
-            if installation_auth is None:
+            if not installation_auth is None:
                 # The App credentials must be loaded before an installation auth
                 # can be derived from them.
                 self._ensure_initialized()
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x74adedc8e350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='128290309527536'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x74adea955130>
installation_id = 123456

    def _get_installation_client(self, installation_id: int) -> Github:
        """Get an authenticated GitHub client for an installation.
    
        Every call builds a private lazy client on credentials shared per
        installation, so the token is reused while connection state stays
        private to the calling thread. Binding the client gives the credentials
        the requester PyGithub needs for an exchange. The token is then read
        here, before any repository request, so the first exchange or a due
        refresh happens now and an authentication failure never looks like a
        missing repository. A failed exchange leaves the credentials in place;
        the next read retries.
    
        Args:
            installation_id: GitHub App installation ID.
    
        Returns:
            Authenticated Github client instance with timeout and retry configured.
    
        Raises:
            github.GithubException: If token exchange fails.
        """
        installation_auth = self._installation_auth(installation_id)
        client = self._new_client(installation_auth)
>       installation_auth.token  # noqa: B018
        ^^^^^^^^^^^^^^^^^^^^^^^
E       AttributeError: 'NoneType' object has no attribute 'token'

stampbot/github_client.py:354: AttributeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - AttributeError: 'NoneType' object has no attribute 'token'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.44s
operator: core/AddNot, occurrence: 18, definition_name: _installation_auth
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -307,7 +307,7 @@
                 # The App credentials must be loaded before an installation auth
                 # can be derived from them.
                 self._ensure_initialized()
-                assert self._auth is not None  # guaranteed by _ensure_initialized
+                assert not self._auth is not None  # guaranteed by _ensure_initialized
                 installation_auth = InstrumentedInstallationAuth(self._auth, installation_id)
                 self._installation_auths[installation_id] = installation_auth
                 self._prune_rate_limit_metrics()
........................................................................ [ 15%]
..................................................F
=================================== FAILURES ===================================
________ TestGetInstallationClient.test_get_installation_client_success ________

self = <tests.test_github_client.TestGetInstallationClient object at 0x7e5b9cb7e350>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='138931878778864'>

    def test_get_installation_client_success(self, installation_auth_cls):
        """Test successful installation client creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client._get_installation_client(123456)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:325: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:352: in _get_installation_client
    installation_auth = self._installation_auth(installation_id)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e5b998bd130>
installation_id = 123456

    def _installation_auth(self, installation_id: int) -> InstrumentedInstallationAuth:
        """Return the shared credentials for an installation, creating them on first use.
    
        No network request happens here, so the cache lock is held only briefly.
        The token exchange happens when a caller reads ``token``, serialized per
        installation by the credentials themselves.
    
        Args:
            installation_id: GitHub App installation ID.
    
        Returns:
            Credentials whose token PyGithub exchanges on first read and
            refreshes before GitHub expires it.
    
        Raises:
            RuntimeError: If the GitHub App credentials are not configured.
        """
        with self._installation_auths_lock:
            installation_auth = self._installation_auths.get(installation_id)
            if installation_auth is None:
                # The App credentials must be loaded before an installation auth
                # can be derived from them.
                self._ensure_initialized()
>               assert not self._auth is not None  # guaranteed by _ensure_initialized
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^
E               AssertionError

stampbot/github_client.py:310: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_success - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 122 passed, 3 deselected, 1 warning in 1.39s
operator: core/AddNot, occurrence: 19, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if not remaining < 0 or limit < 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................F
=================================== FAILURES ===================================
_ TestRateLimitLabelPruning.test_prune_removes_labels_for_evicted_installations _

self = <tests.test_github_client.TestRateLimitLabelPruning object at 0x7440222825d0>

    def test_prune_removes_labels_for_evicted_installations(self):
        """Test gauge label sets follow the credential cache, so churn cannot grow them."""
        from stampbot.github_client import GitHubAppClient
        from stampbot.metrics import github_api_rate_limit_limit, github_api_rate_limit_remaining
    
        client = GitHubAppClient()
        for installation_id in (9001, 9002):
            fake = Mock()
            fake.requester.rate_limiting = (10, 5000)
            client._update_rate_limit_metrics(fake, installation_id)
>       assert client._rate_limit_installations == {"9001", "9002"}
E       AssertionError: assert set() == {'9001', '9002'}
E         
E         Extra items in the right set:
E         '9002'
E         '9001'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + set()
E         - {
E         -     '9001',
E         -     '9002',
E         - }

tests/test_github_client.py:519: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRateLimitLabelPruning::test_prune_removes_labels_for_evicted_installations - AssertionError: assert set() == {'9001', '9002'}
  
  Extra items in the right set:
  '9002'
  '9001'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + set()
  - {
  -     '9001',
  -     '9002',
  - }
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 128 passed, 3 deselected, 1 warning in 1.41s
operator: core/AddNot, occurrence: 20, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if not self._bot_login_value is not None and self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x73dead11f4d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:36:52.652022Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: 'NoneType' object is not subscriptable [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "'NoneType' object is not subscriptable"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "'NoneType' object is not subscriptable"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: 'NoneType' object is not subscriptable", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:36:52.652022Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.47s
operator: core/AddNot, occurrence: 21, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if not e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
..................................................................F
=================================== FAILURES ===================================
____ TestDismissApproval.test_dismiss_approval_already_dismissed_is_success ____

self = <tests.test_github_client.TestDismissApproval object at 0x7dbc0e373110>

    def test_dismiss_approval_already_dismissed_is_success(self):
        """Test GitHub's 422 for an already dismissed review counts as done."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "DISMISSED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Can not dismiss a dismissed pull request review"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is True
E           AssertionError: assert False is True
E            +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7dbc0b017bb0>.dismiss_approval

tests/test_github_client.py:828: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:17:55.997375Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"} [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}, 'event': 'Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"}', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:17:55.997375Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_already_dismissed_is_success - AssertionError: assert False is True
 +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7dbc0b017bb0>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected, 1 warning in 1.42s
operator: core/AddNot, occurrence: 22, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -681,7 +681,7 @@
                 repo = client.get_repo(repo_full_name)
 
                 content = _get_repo_contents(repo, file_path, ref)
-                if isinstance(content, list):
+                if not isinstance(content, list):
                     duration = time.time() - start_time
                     github_api_request_duration_seconds.labels(operation="get_file").observe(
                         duration
........................................................................ [ 15%]
......................................................................F
=================================== FAILURES ===================================
__________________ TestGetRepoFile.test_get_repo_file_success __________________

self = <tests.test_github_client.TestGetRepoFile object at 0x769e94b9b390>

    def test_get_repo_file_success(self):
        """Test successful file retrieval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_content = Mock()
            mock_content.decoded_content = b"file content"
            mock_repo = Mock()
            mock_repo.get_contents.return_value = mock_content
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_repo_file(
                123456,
                "owner/repo",
                "stampbot.toml",
                ref="main",
            )
    
>           assert result == "file content"
E           AssertionError: assert None == 'file content'

tests/test_github_client.py:985: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_success - AssertionError: assert None == 'file content'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 142 passed, 3 deselected, 1 warning in 1.43s
operator: core/AddNot, occurrence: 23, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -706,7 +706,7 @@
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
-                if e.status == 404 and repo is not None:
+                if not e.status == 404 and repo is not None:
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x7cc75dd515b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='137195658490752'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:07:16.389107Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:07:16.389107Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.44s
operator: core/AddNot, occurrence: 24, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -712,7 +712,7 @@
                     )
                 repository_is_optionally_missing = not_found == "repository"
                 file_is_confirmed_missing = not_found == "file"
-                if repository_is_optionally_missing or file_is_confirmed_missing:
+                if not repository_is_optionally_missing or file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
 
                     add_span_attributes(span, {"github.result": "not_found"})
........................................................................ [ 15%]
........................................................................ [ 30%]
.F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_propagates_unconfirmed_not_found[root_result0] _

self = <tests.test_github_client.TestGetRepoFile object at 0x74116ea456e0>
root_result = GithubException(404 {"message": "Not Found"})

    @pytest.mark.parametrize(
        "root_result",
        [
            GithubException(404, {"message": "Not Found"}, None),
            GithubException(403, {"message": "Forbidden"}, None),
            TimeoutError("GitHub root read timed out"),
            Mock(),
        ],
    )
    def test_get_repo_file_propagates_unconfirmed_not_found(self, root_result):
        """Test a contents 404 fails closed unless the same ref remains readable."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            first = GithubException(404, {"message": "Not Found"}, None)
            mock_repo.get_contents.side_effect = [first, root_result]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(GithubException) as error:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE GithubException

tests/test_github_client.py:1110: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_unconfirmed_not_found[root_result0] - Failed: DID NOT RAISE GithubException
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 145 passed, 3 deselected, 1 warning in 1.45s
operator: core/AddNot, occurrence: 25, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -718,7 +718,7 @@
                     add_span_attributes(span, {"github.result": "not_found"})
                     set_span_ok(span)
 
-                    if repository_is_optionally_missing:
+                    if not repository_is_optionally_missing:
                         logger.debug(
                             "Optional policy repository %s was not found",
                             repo_full_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/AddNot, occurrence: 26, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -812,7 +812,7 @@
 
                 bot_reviews: list[BotReview] = []
                 for review in pr.get_reviews():
-                    if review.user.login == bot_user and review.state in (
+                    if not review.user.login == bot_user and review.state in (
                         "APPROVED",
                         "DISMISSED",
                     ):
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x768d703034d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert [{'id': 456, ...9815890000'>}] == [{'id': 123, ...d': 'oldsha'}]
E             
E             At index 0 diff: {'id': 456, 'state': 'APPROVED', 'commit_id': <Mock name='mock.commit_id' id='130349815890000'>} != {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
E             Right contains one more item: {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   {
E             -         'id': 123,
E             ?               ^^^
E             +         'id': 456,
E             ?               ^^^
E                       'state': 'APPROVED',
E             +         'commit_id': <Mock name='mock.commit_id' id='130349815890000'>,
E             -         'commit_id': 'headsha',
E             -     },
E             -     {
E             -         'id': 124,
E             -         'state': 'DISMISSED',
E             -         'commit_id': 'oldsha',
E                   },
E               ]

tests/test_github_client.py:1481: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert [{'id': 456, ...9815890000'>}] == [{'id': 123, ...d': 'oldsha'}]
  
  At index 0 diff: {'id': 456, 'state': 'APPROVED', 'commit_id': <Mock name='mock.commit_id' id='130349815890000'>} != {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
  Right contains one more item: {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        {
  -         'id': 123,
  ?               ^^^
  +         'id': 456,
  ?               ^^^
            'state': 'APPROVED',
  +         'commit_id': <Mock name='mock.commit_id' id='130349815890000'>,
  -         'commit_id': 'headsha',
  -     },
  -     {
  -         'id': 124,
  -         'state': 'DISMISSED',
  -         'commit_id': 'oldsha',
        },
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.45s
operator: core/AddNot, occurrence: 27, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1067,7 +1067,7 @@
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
-                if e.status == 404:
+                if not e.status == 404:
                     add_span_attributes(span, {"github.label_found": False})
                     set_span_ok(span)
                     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x7d0087b3fd90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "missing")
    
>           assert result is False
E           assert None is False

tests/test_github_client.py:1977: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.46s
operator: core/AddNot, occurrence: 28, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1231,7 +1231,7 @@
 
                 for team_ref in allowed_teams:
                     # Extract team slug (handle both "org/team" and "team" formats)
-                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+                    team_slug = team_ref.split("/")[-1] if not "/" in team_ref else team_ref
 
                     try:
                         team = org.get_team_by_slug(team_slug)
........................................................................ [ 15%]
........................................................................ [ 30%]
.....................F
=================================== FAILURES ===================================
________ TestGetUserTeamSlugs.test_get_user_team_slugs_with_org_prefix _________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x715fd7a57b10>

    def test_get_user_team_slugs_with_org_prefix(self):
        """Test team membership check with org/team format."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_user = Mock()
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(123456, "acme", "alice", ["acme/release-team"])
    
            # Should extract "release-team" from "acme/release-team"
>           mock_org.get_team_by_slug.assert_called_with("release-team")

tests/test_github_client.py:1775: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization().get_team_by_slug' id='124656423654064'>
args = ('release-team',), kwargs = {}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: get_team_by_slug('release-team')
E             Actual: get_team_by_slug('acme/release-team')

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_with_org_prefix - AssertionError: expected call not found.
Expected: get_team_by_slug('release-team')
  Actual: get_team_by_slug('acme/release-team')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 165 passed, 3 deselected, 1 warning in 1.50s
operator: core/AddNot, occurrence: 29, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1236,7 +1236,7 @@
                     try:
                         team = org.get_team_by_slug(team_slug)
                         # Check if user is a member
-                        if team.has_in_members(user):  # type: ignore[arg-type]
+                        if not team.has_in_members(user):  # type: ignore[arg-type]
                             member_teams.append(team_slug)
                             logger.debug(
                                 "User %s is a member of team %s",
........................................................................ [ 15%]
........................................................................ [ 30%]
....................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7390f26f79d0>

    def test_get_user_team_slugs_success(self):
        """Test successful team membership check."""
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Mock user
            mock_user = Mock()
            mock_user.login = "alice"
    
            # Mock team that has alice as member
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
    
            # Mock org
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(
                123456, "acme", "alice", ["release-team", "deploy-team"]
            )
    
>           assert result == ["release-team", "deploy-team"]
E           AssertionError: assert [] == ['release-tea...'deploy-team']
E             
E             Right contains 2 more items, first extra item: 'release-team'
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E             + []
E             - [
E             -     'release-team',
E             -     'deploy-team',
E             - ]

tests/test_github_client.py:1731: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
  
  Right contains 2 more items, first extra item: 'release-team'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     'release-team',
  -     'deploy-team',
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 164 passed, 3 deselected, 1 warning in 1.47s
operator: core/AddNot, occurrence: 30, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1253,7 +1253,7 @@
                         logger.debug(
                             "Could not check team %s membership: %s",
                             team_slug,
-                            e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
+                            e.data.get("message", str(e)) if not hasattr(e, "data") else str(e),
                             extra={
                                 "org": org_name,
                                 "team": team_slug,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceTrueWithFalse, occurrence: 0, definition_name: _ensure_initialized
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -192,7 +192,7 @@
                 timeout=GITHUB_API_TIMEOUT,
                 retry=_build_retry(),
             )
-            self._initialized = True
+            self._initialized = False
 
     @property
     def integration(self) -> GithubIntegration:
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x773231207ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            client._ensure_initialized()
    
>           assert client._initialized is True
E           assert False is True
E            +  where False = <stampbot.github_client.GitHubAppClient object at 0x77322df449e0>._initialized

tests/test_github_client.py:116: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - assert False is True
 +  where False = <stampbot.github_client.GitHubAppClient object at 0x77322df449e0>._initialized
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceTrueWithFalse, occurrence: 1, definition_name: _new_client
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -281,7 +281,7 @@
             auth=installation_auth,
             timeout=GITHUB_API_TIMEOUT,
             retry=_build_retry(),
-            lazy=True,
+            lazy=False,
         )
 
     def _installation_auth(self, installation_id: int) -> InstrumentedInstallationAuth:
........................................................................ [ 15%]
....................................................F
=================================== FAILURES ===================================
_ TestGetInstallationClient.test_get_installation_client_reuses_credentials_per_installation _

self = <tests.test_github_client.TestGetInstallationClient object at 0x7a1410644fc0>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='134226534282784'>

    def test_get_installation_client_reuses_credentials_per_installation(
        self, installation_auth_cls
    ):
        """Test credentials are shared per installation while each call gets its own client."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            mock_github_cls.side_effect = lambda **kwargs: Mock(name="github", init_kwargs=kwargs)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            first = client._get_installation_client(111)
            second = client._get_installation_client(111)
            other = client._get_installation_client(222)
    
            # A PyGithub client keeps in-flight request state on its connection,
            # so every call gets a private client bound to shared credentials.
            assert first is not second
            assert other is not first
            # Three calls build one client each; credentials are created once per installation.
            assert mock_github_cls.call_count == 3
            assert installation_auth_cls.call_args_list == [
                call(mock_auth_cls.return_value, 111),
                call(mock_auth_cls.return_value, 222),
            ]
            kwargs = mock_github_cls.call_args_list[0].kwargs
>           assert kwargs["lazy"] is True
E           assert False is True

tests/test_github_client.py:397: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_reuses_credentials_per_installation - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 124 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceTrueWithFalse, occurrence: 2, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -456,7 +456,7 @@
                         "installation_id": installation_id,
                     },
                 )
-                return True
+                return False
 
             except Exception as e:
                 duration = time.time() - start_time
........................................................................ [ 15%]
.............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_success _____________________

self = <tests.test_github_client.TestApprovePR object at 0x79c543b8aad0>

    def test_approve_pr_success(self):
        """Test successful PR approval."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.approve_pr(123456, "owner/repo", 42)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:631: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:43:08.745801Z [info     ] Approved PR #42 in owner/repo  [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=info extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
INFO     stampbot.github_client:github_client.py:451 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456}, 'event': 'Approved PR #42 in owner/repo', 'logger': 'stampbot.github_client', 'level': 'info', 'timestamp': '2026-11-04T03:43:08.745801Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 133 passed, 3 deselected, 1 warning in 1.41s
operator: core/ReplaceTrueWithFalse, occurrence: 3, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -617,7 +617,7 @@
                         "review_id": review_id,
                     },
                 )
-                return True
+                return False
 
             except Exception as e:
                 duration = time.time() - start_time
........................................................................ [ 15%]
.................................................................F
=================================== FAILURES ===================================
______________ TestDismissApproval.test_dismiss_approval_success _______________

self = <tests.test_github_client.TestDismissApproval object at 0x712c53c82fd0>

    def test_dismiss_approval_success(self):
        """Test successful approval dismissal."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_review = Mock()
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.dismiss_approval(123456, "owner/repo", 42, 999)
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:791: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:26:37.476055Z [info     ] Dismissed approval for PR #42 in owner/repo [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=info extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'review_id': 999}
------------------------------ Captured log call -------------------------------
INFO     stampbot.github_client:github_client.py:611 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'review_id': 999}, 'event': 'Dismissed approval for PR #42 in owner/repo', 'logger': 'stampbot.github_client', 'level': 'info', 'timestamp': '2026-11-04T04:26:37.476055Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 137 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceTrueWithFalse, occurrence: 4, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -916,7 +916,7 @@
                         "installation_id": installation_id,
                     },
                 )
-                return True
+                return False
 
             except Exception as e:
                 duration = time.time() - start_time
........................................................................ [ 15%]
........................................................................ [ 30%]
.............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_success ________

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7a779709fed0>

    def test_create_pr_review_comment_success(self):
        """Test successful review comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2098: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:50:58.536881Z [info     ] Posted config error review on PR #42 in owner/repo [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=info extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
INFO     stampbot.github_client:github_client.py:909 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456}, 'event': 'Posted config error review on PR #42 in owner/repo', 'logger': 'stampbot.github_client', 'level': 'info', 'timestamp': '2026-11-04T03:50:58.536881Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 173 passed, 3 deselected, 1 warning in 1.50s
operator: core/ReplaceTrueWithFalse, occurrence: 5, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -995,7 +995,7 @@
                         "installation_id": installation_id,
                     },
                 )
-                return True
+                return False
 
             except Exception as e:
                 duration = time.time() - start_time
........................................................................ [ 15%]
........................................................................ [ 30%]
...............................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_success ___________

self = <tests.test_github_client.TestCreateIssueComment object at 0x7f420d6a0190>

    def test_create_issue_comment_success(self):
        """Test successful issue comment creation."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_issue = Mock()
            mock_repo = Mock()
            mock_repo.get_issue.return_value = mock_issue
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:2187: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:42:46.242065Z [info     ] Posted issue comment on #42 in owner/repo [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=info extra={'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
INFO     stampbot.github_client:github_client.py:988 {'extra': {'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456}, 'event': 'Posted issue comment on #42 in owner/repo', 'logger': 'stampbot.github_client', 'level': 'info', 'timestamp': '2026-11-04T04:42:46.242065Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_success - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 175 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceTrueWithFalse, occurrence: 6, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1058,7 +1058,7 @@
 
                 self._update_rate_limit_metrics(client, installation_id)
 
-                add_span_attributes(span, {"github.label_found": True})
+                add_span_attributes(span, {"github.label_found": False})
                 set_span_ok(span)
                 return True
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceTrueWithFalse, occurrence: 7, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1060,7 +1060,7 @@
 
                 add_span_attributes(span, {"github.label_found": True})
                 set_span_ok(span)
-                return True
+                return False
 
             except GithubException as e:
                 duration = time.time() - start_time
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_true ___________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x7e4e36d87c50>

    def test_repo_has_label_true(self):
        """Test repo_has_label returns True when label exists."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.return_value = Mock()
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=1, limit=2))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "autoapprove")
    
>           assert result is True
E           assert False is True

tests/test_github_client.py:1937: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_true - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 169 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceFalseWithTrue, occurrence: 0, definition_name: _can_read_repo_root
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -88,7 +88,7 @@
     try:
         root = _get_repo_contents(repo, "", ref)
     except Exception:
-        return False
+        return True
     return isinstance(root, list)
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_propagates_unconfirmed_not_found[root_result0] _

self = <tests.test_github_client.TestGetRepoFile object at 0x703ae96496e0>
root_result = GithubException(404 {"message": "Not Found"})

    @pytest.mark.parametrize(
        "root_result",
        [
            GithubException(404, {"message": "Not Found"}, None),
            GithubException(403, {"message": "Forbidden"}, None),
            TimeoutError("GitHub root read timed out"),
            Mock(),
        ],
    )
    def test_get_repo_file_propagates_unconfirmed_not_found(self, root_result):
        """Test a contents 404 fails closed unless the same ref remains readable."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            first = GithubException(404, {"message": "Not Found"}, None)
            mock_repo.get_contents.side_effect = [first, root_result]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(GithubException) as error:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE GithubException

tests/test_github_client.py:1110: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_unconfirmed_not_found[root_result0] - Failed: DID NOT RAISE GithubException
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 145 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceFalseWithTrue, occurrence: 1, definition_name: _repository_is_missing
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -104,7 +104,7 @@
     except GithubException as e:
         return e.status == 404
     except Exception:
-        return False
+        return True
     return False
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................................F
=================================== FAILURES ===================================
_ TestRepositoryIsMissing.test_repository_is_missing_is_false_unless_github_says_404[outcome2] _

self = <tests.test_github_client.TestRepositoryIsMissing object at 0x79298b5f22c0>
outcome = TimeoutError('GitHub read timed out')

    @pytest.mark.parametrize(
        "outcome",
        [
            None,
            GithubException(403, {"message": "Forbidden"}, None),
            TimeoutError("GitHub read timed out"),
        ],
    )
    def test_repository_is_missing_is_false_unless_github_says_404(self, outcome):
        """Test success and every other failure keep the fail-closed path."""
        from stampbot.github_client import _repository_is_missing
    
        repo = Mock()
        if outcome is not None:
            repo.complete.side_effect = outcome
    
>       assert _repository_is_missing(repo) is False
E       AssertionError: assert True is False
E        +  where True = <function _repository_is_missing at 0x7929887317a0>(<Mock id='133219305510896'>)

tests/test_github_client.py:2317: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepositoryIsMissing::test_repository_is_missing_is_false_unless_github_says_404[outcome2] - AssertionError: assert True is False
 +  where True = <function _repository_is_missing at 0x7929887317a0>(<Mock id='133219305510896'>)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 185 passed, 3 deselected, 1 warning in 1.54s
operator: core/ReplaceFalseWithTrue, occurrence: 2, definition_name: _repository_is_missing
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -105,7 +105,7 @@
         return e.status == 404
     except Exception:
         return False
-    return False
+    return True
 
 
 NotFoundKind = Literal["repository", "file", "unconfirmed"]
........................................................................ [ 15%]
........................................................................ [ 30%]
..........F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_optional_repository_does_not_hide_unconfirmed_file_not_found _

self = <tests.test_github_client.TestGetRepoFile object at 0x74c735e14910>

    def test_optional_repository_does_not_hide_unconfirmed_file_not_found(self):
        """Test optional scope does not weaken policy-file read failures."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            first = GithubException(404, {"message": "Not Found"}, None)
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                first,
                GithubException(404, {"message": "Not Found"}, None),
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(GithubException) as error:
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE GithubException

tests/test_github_client.py:1359: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_optional_repository_does_not_hide_unconfirmed_file_not_found - Failed: DID NOT RAISE GithubException
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 154 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceFalseWithTrue, occurrence: 3, definition_name: __init__
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -143,7 +143,7 @@
         """Initialize GitHub App client (lazy initialization)."""
         self._auth: Auth.AppAuth | None = None
         self._integration: GithubIntegration | None = None
-        self._initialized = False
+        self._initialized = True
         # First use from several webhook threads must build the App credentials once.
         self._init_lock = threading.Lock()
         # Installation credentials are shared across webhook threads. PyGithub
........................................................................ [ 15%]
..............................F
=================================== FAILURES ===================================
________ TestGitHubAppClientInit.test_init_creates_uninitialized_client ________

self = <tests.test_github_client.TestGitHubAppClientInit object at 0x7e803f705950>

    def test_init_creates_uninitialized_client(self):
        """Test that __init__ creates an uninitialized client."""
        from stampbot.github_client import GitHubAppClient
    
        client = GitHubAppClient()
        assert client._auth is None
        assert client._integration is None
>       assert client._initialized is False
E       assert True is False
E        +  where True = <stampbot.github_client.GitHubAppClient object at 0x7e8037ea9810>._initialized

tests/test_github_client.py:39: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGitHubAppClientInit::test_init_creates_uninitialized_client - assert True is False
 +  where True = <stampbot.github_client.GitHubAppClient object at 0x7e8037ea9810>._initialized
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 102 passed, 3 deselected, 1 warning in 1.37s
operator: core/ReplaceFalseWithTrue, occurrence: 4, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -474,7 +474,7 @@
                         "error": _sanitize_error(e),
                     },
                 )
-                return False
+                return True
 
     def get_pr_head_sha(
         self,
........................................................................ [ 15%]
..............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________

self = <tests.test_github_client.TestApprovePR object at 0x79b9cb87ec10>

    def test_approve_pr_failure(self):
        """Test PR approval failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.create_review.side_effect = Exception("API Error")
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.approve_pr(123456, "owner/repo", 42)
    
>           assert result is False
E           assert True is False

tests/test_github_client.py:670: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:31:17.447339Z [error    ] Failed to approve PR #42 in owner/repo: API Error [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': 'API Error'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:468 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': 'API Error'}, 'event': 'Failed to approve PR #42 in owner/repo: API Error', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:31:17.447339Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceFalseWithTrue, occurrence: 5, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -638,7 +638,7 @@
                         "error": _sanitize_error(e),
                     },
                 )
-                return False
+                return True
 
     def get_repo_file(
         self,
........................................................................ [ 15%]
...................................................................F
=================================== FAILURES ===================================
_ TestDismissApproval.test_dismiss_approval_422_with_review_still_approved_fails _

self = <tests.test_github_client.TestDismissApproval object at 0x70ff01f2d350>

    def test_dismiss_approval_422_with_review_still_approved_fails(self):
        """Test a 422 that leaves the review approved is reported as a failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "APPROVED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Validation Failed"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is False
E           AssertionError: assert True is False
E            +  where True = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x70fefeb87b10>.dismiss_approval

tests/test_github_client.py:864: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:26:55.262970Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Validation Failed"} [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Validation Failed"}'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Validation Failed"}'}, 'event': 'Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Validation Failed"}', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T04:26:55.262970Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_422_with_review_still_approved_fails - AssertionError: assert True is False
 +  where True = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x70fefeb87b10>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 139 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceFalseWithTrue, occurrence: 6, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -647,7 +647,7 @@
         file_path: str,
         ref: str | None = None,
         *,
-        missing_repository_is_optional: bool = False,
+        missing_repository_is_optional: bool = True,
     ) -> str | None:
         """Get file content from repository.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_target_repository_404_never_checks_repository _

self = <tests.test_github_client.TestGetRepoFile object at 0x7ca0971ecf50>

    def test_get_repo_file_target_repository_404_never_checks_repository(self):
        """Test the target repository never spends a request to become optional."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration_cls.return_value = Mock()
    
            first = GithubException(404, {"message": "Not Found"}, None)
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                first,
                GithubException(404, {"message": "Not Found"}, None),
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(GithubException) as error:
                client.get_repo_file(123456, "owner/repo", "stampbot.toml")
    
            assert error.value is first
>           mock_repo.complete.assert_not_called()

tests/test_github_client.py:1284: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().complete' id='137029048426112'>

    def assert_not_called(self):
        """assert that the mock was never called.
        """
        if self.call_count != 0:
            msg = ("Expected '%s' to not have been called. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'complete' to not have been called. Called 1 times.
E           Calls: [call()].

/usr/lib/python3.14/unittest/mock.py:947: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:03:49.976443Z [warning  ] GitHub could not read stampbot.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:03:49.976443Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_target_repository_404_never_checks_repository - AssertionError: Expected 'complete' to not have been called. Called 1 times.
Calls: [call()].
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 152 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceFalseWithTrue, occurrence: 7, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -937,7 +937,7 @@
                         "error": _sanitize_error(e),
                     },
                 )
-                return False
+                return True
 
     def create_issue_comment(
         self,
........................................................................ [ 15%]
........................................................................ [ 30%]
..............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x734f1da98050>

    def test_create_pr_review_comment_failure(self):
        """Test review comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_pull.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )
    
>           assert result is False
E           assert True is False

tests/test_github_client.py:2140: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:29:35.219046Z [warning  ] Failed to post config error review for PR #42 in owner/repo: API Error [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': 'API Error'}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:928 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': 'API Error'}, 'event': 'Failed to post config error review for PR #42 in owner/repo: API Error', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:29:35.219046Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 174 passed, 3 deselected, 1 warning in 1.52s
operator: core/ReplaceFalseWithTrue, occurrence: 8, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1018,7 +1018,7 @@
                         "error": _sanitize_error(e),
                     },
                 )
-                return False
+                return True
 
     def repo_has_label(
         self,
........................................................................ [ 15%]
........................................................................ [ 30%]
................................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_failure ___________

self = <tests.test_github_client.TestCreateIssueComment object at 0x77b660e342d0>

    def test_create_issue_comment_failure(self):
        """Test issue comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_issue.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )
    
>           assert result is False
E           assert True is False

tests/test_github_client.py:2230: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:03:00.967457Z [warning  ] Failed to post issue comment on #42 in owner/repo: API Error [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': 'API Error'}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1009 {'extra': {'repo': 'owner/repo', 'issue_number': 42, 'installation_id': 123456, 'error': 'API Error'}, 'event': 'Failed to post issue comment on #42 in owner/repo: API Error', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:03:00.967457Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_failure - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 176 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceFalseWithTrue, occurrence: 9, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1068,7 +1068,7 @@
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
                 if e.status == 404:
-                    add_span_attributes(span, {"github.label_found": False})
+                    add_span_attributes(span, {"github.label_found": True})
                     set_span_ok(span)
                     return False
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceFalseWithTrue, occurrence: 10, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1070,7 +1070,7 @@
                 if e.status == 404:
                     add_span_attributes(span, {"github.label_found": False})
                     set_span_ok(span)
-                    return False
+                    return True
 
                 set_span_error(span, e)
                 logger.debug(
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x71a4b7b87d90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "missing")
    
>           assert result is False
E           assert True is False

tests/test_github_client.py:1977: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceFalseWithTrue, occurrence: 11, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1191,7 +1191,7 @@
                         "error": _sanitize_error(e),
                     },
                 )
-                return False
+                return True
 
     def get_user_team_slugs(
         self,
........................................................................ [ 15%]
........................................................................ [ 30%]
..................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________

self = <tests.test_github_client.TestUserHasPermission object at 0x7ef0a9515810>

    def test_user_has_permission_error(self):
        """Test permission check returns False on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
    
>           assert result is False
E           assert True is False

tests/test_github_client.py:1639: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:28:02.440802Z [warning  ] Failed to check collaborator permission for carol in owner/repo: API Error [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'username': 'carol', 'installation_id': 123456, 'error': 'API Error'}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:1182 {'extra': {'repo': 'owner/repo', 'username': 'carol', 'installation_id': 123456, 'error': 'API Error'}, 'event': 'Failed to check collaborator permission for carol in owner/repo: API Error', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:28:02.440802Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 162 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceAndWithOr, occurrence: 0, definition_name: _classify_not_found
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -119,7 +119,7 @@
     ``.github`` repository. A readable repository root proves the file is
     absent. Anything else stays unconfirmed and the caller fails closed.
     """
-    if optional_repo and _repository_is_missing(repo):
+    if optional_repo or _repository_is_missing(repo):
         return "repository"
     if _can_read_repo_root(repo, ref):
         return "file"
........................................................................ [ 15%]
........................................................................ [ 30%]
.......F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_accepts_missing_optional_repository_with_lazy_client _

self = <tests.test_github_client.TestGetRepoFile object at 0x7ea1545e8d70>

    def test_get_repo_file_accepts_missing_optional_repository_with_lazy_client(self):
        """Test a lazy client confirms the optional repository itself is missing."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_integration_cls.return_value = mock_integration
    
            # Lazy get_repo never raises. The file read and the root read both
            # 404, and only the repository request proves the repo is absent.
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                GithubException(404, {"message": "Not Found"}, None),
            ]
            mock_repo.complete.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_repo_file(
                123456,
                "owner/.github",
                "stampbot.toml",
                missing_repository_is_optional=True,
            )
    
            assert result is None
>           mock_repo.complete.assert_called_once_with()

tests/test_github_client.py:1244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().complete' id='139231315622416'>
args = (), kwargs = {}

    def assert_called_once_with(self, /, *args, **kwargs):
        """assert that the mock was called exactly once and that that call was
        with the specified arguments."""
        if not self.call_count == 1:
            msg = ("Expected '%s' to be called once. Called %s times.%s"
                   % (self._mock_name or 'mock',
                      self.call_count,
                      self._calls_repr()))
>           raise AssertionError(msg)
E           AssertionError: Expected 'complete' to be called once. Called 0 times.

/usr/lib/python3.14/unittest/mock.py:997: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_accepts_missing_optional_repository_with_lazy_client - AssertionError: Expected 'complete' to be called once. Called 0 times.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 151 passed, 3 deselected, 1 warning in 1.44s
operator: core/ReplaceAndWithOr, occurrence: 1, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if self._bot_login_value is not None or self._bot_login_value[1] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7e6bc07134d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]

tests/test_github_client.py:1481: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:29:07.226075Z [error    ] Failed to find bot approval reviews for PR #42 in owner/repo: 'NoneType' object is not subscriptable [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "'NoneType' object is not subscriptable"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:851 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "'NoneType' object is not subscriptable"}, 'event': "Failed to find bot approval reviews for PR #42 in owner/repo: 'NoneType' object is not subscriptable", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:29:07.226075Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert None == [{'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}, {'id': 124, 'state': 'DISMISSED', 'commit_id': 'oldsha'}]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceAndWithOr, occurrence: 2, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -706,7 +706,7 @@
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
-                if e.status == 404 and repo is not None:
+                if e.status == 404 or repo is not None:
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceAndWithOr, occurrence: 3, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -812,7 +812,7 @@
 
                 bot_reviews: list[BotReview] = []
                 for review in pr.get_reviews():
-                    if review.user.login == bot_user and review.state in (
+                    if review.user.login == bot_user or review.state in (
                         "APPROVED",
                         "DISMISSED",
                     ):
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7253ccf834d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert [{'id': 123, ...4217559456'>}] == [{'id': 123, ...d': 'oldsha'}]
E             
E             Left contains 2 more items, first extra item: {'id': 125, 'state': 'COMMENTED', 'commit_id': <Mock name='mock.commit_id' id='125704217559120'>}
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E               [
E                   {
E                       'id': 123,
E                       'state': 'APPROVED',
E                       'commit_id': 'headsha',
E                   },
E                   {
E                       'id': 124,
E                       'state': 'DISMISSED',
E                       'commit_id': 'oldsha',
E                   },
E             +     {
E             +         'id': 125,
E             +         'state': 'COMMENTED',
E             +         'commit_id': <Mock name='mock.commit_id' id='125704217559120'>,
E             +     },
E             +     {
E             +         'id': 456,
E             +         'state': 'APPROVED',
E             +         'commit_id': <Mock name='mock.commit_id' id='125704217559456'>,
E             +     },
E               ]

tests/test_github_client.py:1481: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert [{'id': 123, ...4217559456'>}] == [{'id': 123, ...d': 'oldsha'}]
  
  Left contains 2 more items, first extra item: {'id': 125, 'state': 'COMMENTED', 'commit_id': <Mock name='mock.commit_id' id='125704217559120'>}
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
        {
            'id': 123,
            'state': 'APPROVED',
            'commit_id': 'headsha',
        },
        {
            'id': 124,
            'state': 'DISMISSED',
            'commit_id': 'oldsha',
        },
  +     {
  +         'id': 125,
  +         'state': 'COMMENTED',
  +         'commit_id': <Mock name='mock.commit_id' id='125704217559120'>,
  +     },
  +     {
  +         'id': 456,
  +         'state': 'APPROVED',
  +         'commit_id': <Mock name='mock.commit_id' id='125704217559456'>,
  +     },
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.42s
operator: core/ReplaceOrWithAnd, occurrence: 0, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if len(pem_lines) < 3 and not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceOrWithAnd, occurrence: 1, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining < 0 and limit < 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceOrWithAnd, occurrence: 2, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status != 422 and pr.get_review(review_id).state != "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
...................................................................F
=================================== FAILURES ===================================
_ TestDismissApproval.test_dismiss_approval_422_with_review_still_approved_fails _

self = <tests.test_github_client.TestDismissApproval object at 0x7dc4dde49350>

    def test_dismiss_approval_422_with_review_still_approved_fails(self):
        """Test a 422 that leaves the review approved is reported as a failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "APPROVED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Validation Failed"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is False
E           AssertionError: assert True is False
E            +  where True = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7dc4daac3bb0>.dismiss_approval

tests/test_github_client.py:864: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:32:30.019875Z [info     ] Review 789 on PR #42 in owner/repo was already dismissed [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=info extra={'repo': 'owner/repo', 'pr_number': 42, 'review_id': 789}
2026-11-04T04:32:30.020040Z [info     ] Dismissed approval for PR #42 in owner/repo [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=info extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'review_id': 789}
------------------------------ Captured log call -------------------------------
INFO     stampbot.github_client:github_client.py:590 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'review_id': 789}, 'event': 'Review 789 on PR #42 in owner/repo was already dismissed', 'logger': 'stampbot.github_client', 'level': 'info', 'timestamp': '2026-11-04T04:32:30.019875Z'}
INFO     stampbot.github_client:github_client.py:611 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'review_id': 789}, 'event': 'Dismissed approval for PR #42 in owner/repo', 'logger': 'stampbot.github_client', 'level': 'info', 'timestamp': '2026-11-04T04:32:30.020040Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_422_with_review_still_approved_fails - AssertionError: assert True is False
 +  where True = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7dc4daac3bb0>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 139 passed, 3 deselected, 1 warning in 1.43s
operator: core/ReplaceOrWithAnd, occurrence: 3, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -670,7 +670,7 @@
             {
                 "github.repo": repo_full_name,
                 "github.file_path": file_path,
-                "github.ref": ref or "default",
+                "github.ref": ref and "default",
                 "github.installation_id": installation_id,
             },
         ) as span:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceOrWithAnd, occurrence: 4, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -712,7 +712,7 @@
                     )
                 repository_is_optionally_missing = not_found == "repository"
                 file_is_confirmed_missing = not_found == "file"
-                if repository_is_optionally_missing or file_is_confirmed_missing:
+                if repository_is_optionally_missing and file_is_confirmed_missing:
                     github_api_requests_total.labels(operation="get_file", status="not_found").inc()
 
                     add_span_attributes(span, {"github.result": "not_found"})
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x7b3b2fdc95b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='135494079624064'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:06:25.482899Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:06:25.482899Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceContinueWithBreak, occurrence: 0, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1260,7 +1260,7 @@
                                 "username": username,
                             },
                         )
-                        continue
+                        break
 
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ExceptionReplacer, occurrence: 0, definition_name: _can_read_repo_root
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -87,7 +87,7 @@
     """Confirm that a file 404 did not conceal a repository, auth, or ref failure."""
     try:
         root = _get_repo_contents(repo, "", ref)
-    except Exception:
+    except CosmicRayTestingException:
         return False
     return isinstance(root, list)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_propagates_unconfirmed_not_found[root_result0] _

self = <stampbot.github_client.GitHubAppClient object at 0x76e9a7020ff0>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='130745903140544'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException

During handling of the above exception, another exception occurred:

repo = <Mock name='Github().get_repo()' id='130745903140208'>, ref = None

    def _can_read_repo_root(repo: Repository, ref: str | None) -> bool:
        """Confirm that a file 404 did not conceal a repository, auth, or ref failure."""
        try:
>           root = _get_repo_contents(repo, "", ref)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:89: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='130745903140544'>
args = ('',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x76e9aa65d6e0>
root_result = GithubException(404 {"message": "Not Found"})

    @pytest.mark.parametrize(
        "root_result",
        [
            GithubException(404, {"message": "Not Found"}, None),
            GithubException(403, {"message": "Forbidden"}, None),
            TimeoutError("GitHub root read timed out"),
            Mock(),
        ],
    )
    def test_get_repo_file_propagates_unconfirmed_not_found(self, root_result):
        """Test a contents 404 fails closed unless the same ref remains readable."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            first = GithubException(404, {"message": "Not Found"}, None)
            mock_repo.get_contents.side_effect = [first, root_result]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(GithubException) as error:
>               client.get_repo_file(123456, "owner/repo", "stampbot.toml")

tests/test_github_client.py:1111: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:710: in get_repo_file
    not_found = _classify_not_found(
stampbot/github_client.py:124: in _classify_not_found
    if _can_read_repo_root(repo, ref):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

repo = <Mock name='Github().get_repo()' id='130745903140208'>, ref = None

    def _can_read_repo_root(repo: Repository, ref: str | None) -> bool:
        """Confirm that a file 404 did not conceal a repository, auth, or ref failure."""
        try:
            root = _get_repo_contents(repo, "", ref)
>       except CosmicRayTestingException:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:90: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_unconfirmed_not_found[root_result0] - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 145 passed, 3 deselected, 1 warning in 1.46s
operator: core/ExceptionReplacer, occurrence: 1, definition_name: _repository_is_missing
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -101,7 +101,7 @@
     """
     try:
         repo.complete()
-    except GithubException as e:
+    except CosmicRayTestingException as e:
         return e.status == 404
     except Exception:
         return False
........................................................................ [ 15%]
........................................................................ [ 30%]
.......F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_accepts_missing_optional_repository_with_lazy_client _

self = <stampbot.github_client.GitHubAppClient object at 0x75f7c22022b0>
installation_id = 123456, repo_full_name = 'owner/.github'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = True

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='129706976454912'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException

During handling of the above exception, another exception occurred:

repo = <Mock name='Github().get_repo()' id='129706976454576'>

    def _repository_is_missing(repo: Repository) -> bool:
        """Confirm that GitHub returns 404 for the repository itself.
    
        Lazy repository objects never request ``GET /repos/{owner}/{repo}`` on their
        own, so a contents 404 cannot distinguish a missing file from a missing or
        inaccessible repository. This check settles that question with one request.
        """
        try:
>           repo.complete()

stampbot/github_client.py:103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().complete' id='129706976455248'>
args = (), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x75f7c5954d70>

    def test_get_repo_file_accepts_missing_optional_repository_with_lazy_client(self):
        """Test a lazy client confirms the optional repository itself is missing."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_integration_cls.return_value = mock_integration
    
            # Lazy get_repo never raises. The file read and the root read both
            # 404, and only the repository request proves the repo is absent.
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                GithubException(404, {"message": "Not Found"}, None),
            ]
            mock_repo.complete.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/.github",
                "stampbot.toml",
                missing_repository_is_optional=True,
            )

tests/test_github_client.py:1236: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:710: in get_repo_file
    not_found = _classify_not_found(
stampbot/github_client.py:122: in _classify_not_found
    if optional_repo and _repository_is_missing(repo):
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

repo = <Mock name='Github().get_repo()' id='129706976454576'>

    def _repository_is_missing(repo: Repository) -> bool:
        """Confirm that GitHub returns 404 for the repository itself.
    
        Lazy repository objects never request ``GET /repos/{owner}/{repo}`` on their
        own, so a contents 404 cannot distinguish a missing file from a missing or
        inaccessible repository. This check settles that question with one request.
        """
        try:
            repo.complete()
>       except CosmicRayTestingException as e:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:104: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_accepts_missing_optional_repository_with_lazy_client - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 151 passed, 3 deselected, 1 warning in 1.48s
operator: core/ExceptionReplacer, occurrence: 2, definition_name: _repository_is_missing
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -103,7 +103,7 @@
         repo.complete()
     except GithubException as e:
         return e.status == 404
-    except Exception:
+    except CosmicRayTestingException:
         return False
     return False
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.........................................F
=================================== FAILURES ===================================
_ TestRepositoryIsMissing.test_repository_is_missing_is_false_unless_github_says_404[outcome2] _

repo = <Mock id='126475505227760'>

    def _repository_is_missing(repo: Repository) -> bool:
        """Confirm that GitHub returns 404 for the repository itself.
    
        Lazy repository objects never request ``GET /repos/{owner}/{repo}`` on their
        own, so a contents 404 cannot distinguish a missing file from a missing or
        inaccessible repository. This check settles that question with one request.
        """
        try:
>           repo.complete()

stampbot/github_client.py:103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='mock.complete' id='126475505228096'>, args = (), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               TimeoutError: GitHub read timed out

/usr/lib/python3.14/unittest/mock.py:1241: TimeoutError

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepositoryIsMissing object at 0x73076174e2c0>
outcome = TimeoutError('GitHub read timed out')

    @pytest.mark.parametrize(
        "outcome",
        [
            None,
            GithubException(403, {"message": "Forbidden"}, None),
            TimeoutError("GitHub read timed out"),
        ],
    )
    def test_repository_is_missing_is_false_unless_github_says_404(self, outcome):
        """Test success and every other failure keep the fail-closed path."""
        from stampbot.github_client import _repository_is_missing
    
        repo = Mock()
        if outcome is not None:
            repo.complete.side_effect = outcome
    
>       assert _repository_is_missing(repo) is False
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:2317: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

repo = <Mock id='126475505227760'>

    def _repository_is_missing(repo: Repository) -> bool:
        """Confirm that GitHub returns 404 for the repository itself.
    
        Lazy repository objects never request ``GET /repos/{owner}/{repo}`` on their
        own, so a contents 404 cannot distinguish a missing file from a missing or
        inaccessible repository. This check settles that question with one request.
        """
        try:
            repo.complete()
        except GithubException as e:
            return e.status == 404
>       except CosmicRayTestingException:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:106: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepositoryIsMissing::test_repository_is_missing_is_false_unless_github_says_404[outcome2] - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 185 passed, 3 deselected, 1 warning in 1.58s
operator: core/ExceptionReplacer, occurrence: 3, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -239,7 +239,7 @@
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                     pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
-            except FileNotFoundError:
+            except CosmicRayTestingException:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
                 logger.error("Failed to read private key file: %s", e)
........................................................................ [ 15%]
........................................F
=================================== FAILURES ===================================
________ TestLoadPrivateKey.test_load_private_key_raises_on_file_error _________

self = <stampbot.github_client.GitHubAppClient object at 0x7f6dcae54410>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
>               with Path(key).expanduser().open(encoding="utf-8") as key_file:
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:235: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = PosixPath('/nonexistent/path/to/key.pem'), mode = 'r', buffering = -1
encoding = 'utf-8', errors = None, newline = None

    def open(self, mode='r', buffering=-1, encoding=None,
             errors=None, newline=None):
        """
        Open the file pointed to by this path and return a file object, as
        the built-in open() function does.
        """
        if "b" not in mode:
            encoding = io.text_encoding(encoding)
>       return io.open(self, mode, buffering, encoding, errors, newline)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       FileNotFoundError: [Errno 2] No such file or directory: '/nonexistent/path/to/key.pem'

/usr/lib/python3.14/pathlib/__init__.py:772: FileNotFoundError

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestLoadPrivateKey object at 0x7f6dce244e90>

    def test_load_private_key_raises_on_file_error(self):
        """Test that _load_private_key raises when file doesn't exist."""
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = "/nonexistent/path/to/key.pem"
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(ValueError, match="Invalid private key path"):
>               client._load_private_key()

tests/test_github_client.py:184: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7f6dcae54410>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
>           except CosmicRayTestingException:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:242: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_raises_on_file_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 112 passed, 3 deselected, 1 warning in 1.40s
operator: core/ExceptionReplacer, occurrence: 4, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -241,7 +241,7 @@
                     pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
-            except OSError as e:
+            except CosmicRayTestingException as e:
                 logger.error("Failed to read private key file: %s", e)
                 raise
 
........................................................................ [ 15%]
.........................................F
=================================== FAILURES ===================================
________ TestLoadPrivateKey.test_load_private_key_raises_on_read_error _________

self = <stampbot.github_client.GitHubAppClient object at 0x7bdf67d209f0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
>               with Path(key).expanduser().open(encoding="utf-8") as key_file:
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:235: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='open' id='136199524039520'>, args = ()
kwargs = {'encoding': 'utf-8'}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               PermissionError: Access denied

/usr/lib/python3.14/unittest/mock.py:1241: PermissionError

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestLoadPrivateKey object at 0x7bdf6f6fb9b0>
tmp_path = PosixPath('/tmp/pytest-of-runner/pytest-183/test_load_private_key_raises_o0')

    def test_load_private_key_raises_on_read_error(self, tmp_path):
        """Test that _load_private_key raises when file read fails."""
        key_file = tmp_path / "private-key.pem"
        key_file.write_text("dummy")
    
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = str(key_file)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
            with patch("pathlib.Path.open", side_effect=PermissionError("Access denied")):
                with pytest.raises(PermissionError, match="Access denied"):
>                   client._load_private_key()

tests/test_github_client.py:200: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7bdf67d209f0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:244: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_raises_on_read_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 113 passed, 3 deselected, 1 warning in 1.44s
operator: core/ExceptionReplacer, occurrence: 5, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -378,7 +378,7 @@
             github_api_rate_limit_limit.labels(installation_id=label).set(limit)
             with self._installation_auths_lock:
                 self._rate_limit_installations.add(label)
-        except Exception:
+        except CosmicRayTestingException:
             # Don't fail operations due to rate limit metric errors
             pass
 
........................................................................ [ 15%]
............................................................F
=================================== FAILURES ===================================
_ TestUpdateRateLimitMetrics.test_update_rate_limit_metrics_handles_exception __

self = <stampbot.github_client.GitHubAppClient object at 0x7b2d4d2cb2f0>
client = <Mock id='135434494574672'>, installation_id = 123456

    def _update_rate_limit_metrics(self, client: Github, installation_id: int) -> None:
        """Update rate limit metrics from the client's last GitHub response.
    
        GitHub returns the installation's remaining quota in the
        ``x-ratelimit-*`` headers of every response, so this reads PyGithub's
        record of those headers instead of spending a request on
        ``GET /rate_limit``. The requester is read directly because
        ``Github.rate_limiting`` falls back to that request when no response
        has carried the headers yet.
    
        Args:
            client: Authenticated GitHub client.
            installation_id: GitHub App installation ID for metric labels.
        """
        try:
>           remaining, limit = client.requester.rate_limiting
            ^^^^^^^^^^^^^^^^
E           ValueError: too many values to unpack (expected 2)

stampbot/github_client.py:372: ValueError

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestUpdateRateLimitMetrics object at 0x7b2d50645220>

    def test_update_rate_limit_metrics_handles_exception(self):
        """Test that rate limit metrics errors are silently ignored."""
        from stampbot.github_client import GitHubAppClient
    
        client = GitHubAppClient()
    
        mock_github_client = Mock()
        mock_github_client.requester.rate_limiting = "not a tuple"
    
        # Should not raise - errors are silently ignored
>       client._update_rate_limit_metrics(mock_github_client, 123456)

tests/test_github_client.py:589: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7b2d4d2cb2f0>
client = <Mock id='135434494574672'>, installation_id = 123456

    def _update_rate_limit_metrics(self, client: Github, installation_id: int) -> None:
        """Update rate limit metrics from the client's last GitHub response.
    
        GitHub returns the installation's remaining quota in the
        ``x-ratelimit-*`` headers of every response, so this reads PyGithub's
        record of those headers instead of spending a request on
        ``GET /rate_limit``. The requester is read directly because
        ``Github.rate_limiting`` falls back to that request when no response
        has carried the headers yet.
    
        Args:
            client: Authenticated GitHub client.
            installation_id: GitHub App installation ID for metric labels.
        """
        try:
            remaining, limit = client.requester.rate_limiting
            if remaining < 0 or limit < 0:
                # PyGithub reports -1 until a response has carried the headers.
                return
            label = str(installation_id)
            github_api_rate_limit_remaining.labels(installation_id=label).set(remaining)
            github_api_rate_limit_limit.labels(installation_id=label).set(limit)
            with self._installation_auths_lock:
                self._rate_limit_installations.add(label)
>       except CosmicRayTestingException:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:381: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUpdateRateLimitMetrics::test_update_rate_limit_metrics_handles_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 132 passed, 3 deselected, 1 warning in 1.42s
operator: core/ExceptionReplacer, occurrence: 6, definition_name: approve_pr
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -458,7 +458,7 @@
                 )
                 return True
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                 github_api_requests_total.labels(operation="approve", status="failure").inc()
........................................................................ [ 15%]
..............................................................F
=================================== FAILURES ===================================
____________________ TestApprovePR.test_approve_pr_failure _____________________

self = <stampbot.github_client.GitHubAppClient object at 0x721762d82b70>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
>               pr.create_review(
                    body=comment,
                    event="APPROVE",
                )

stampbot/github_client.py:437: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().create_review' id='125444765251440'>
args = (), kwargs = {'body': 'Auto-approved by Stampbot', 'event': 'APPROVE'}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestApprovePR object at 0x72176604ac10>

    def test_approve_pr_failure(self):
        """Test PR approval failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_pr = Mock()
            mock_pr.create_review.side_effect = Exception("API Error")
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.approve_pr(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:668: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x721762d82b70>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
comment = 'Auto-approved by Stampbot'

    def approve_pr(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        comment: str = "Auto-approved by Stampbot",
    ) -> bool:
        """Approve a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            comment: Review comment
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.approve_pr",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Create approval review
                pr.create_review(
                    body=comment,
                    event="APPROVE",
                )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="approve").observe(duration)
                github_api_requests_total.labels(operation="approve", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "approved"})
                set_span_ok(span)
    
                logger.info(
                    f"Approved PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:461: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestApprovePR::test_approve_pr_failure - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 134 passed, 3 deselected, 1 warning in 1.42s
operator: core/ExceptionReplacer, occurrence: 7, definition_name: get_pr_head_sha
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -519,7 +519,7 @@
 
                 return head_sha
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                 github_api_requests_total.labels(operation="get_pull", status="failure").inc()
........................................................................ [ 15%]
................................................................F
=================================== FAILURES ===================================
_________ TestGetPRHeadSHA.test_get_pr_head_sha_returns_none_on_error __________

self = <stampbot.github_client.GitHubAppClient object at 0x75499d64f7f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:507: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='128959030622400'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetPRHeadSHA object at 0x7549a09b2e90>

    def test_get_pr_head_sha_returns_none_on_error(self):
        """Test PR head SHA lookup returns None on API errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_pr_head_sha(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:745: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x75499d64f7f0>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def get_pr_head_sha(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> str | None:
        """Get the current head SHA for a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Current pull request head SHA, or None on error.
        """
        start_time = time.time()
    
        with create_span(
            "github.get_pr_head_sha",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                head_sha: str = pr.head.sha
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_pull").observe(duration)
                github_api_requests_total.labels(operation="get_pull", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.head_sha": head_sha})
                set_span_ok(span)
    
                return head_sha
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:522: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetPRHeadSHA::test_get_pr_head_sha_returns_none_on_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 136 passed, 3 deselected, 1 warning in 1.46s
operator: core/ExceptionReplacer, occurrence: 8, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -580,7 +580,7 @@
                 review = pr.get_review(review_id)
                 try:
                     review.dismiss(message)
-                except GithubException as e:
+                except CosmicRayTestingException as e:
                     # GitHub answers 422 when a review is already dismissed,
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
........................................................................ [ 15%]
..................................................................F
=================================== FAILURES ===================================
____ TestDismissApproval.test_dismiss_approval_already_dismissed_is_success ____

self = <tests.test_github_client.TestDismissApproval object at 0x7e2edb313110>

    def test_dismiss_approval_already_dismissed_is_success(self):
        """Test GitHub's 422 for an already dismissed review counts as done."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "DISMISSED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Can not dismiss a dismissed pull request review"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is True
E           AssertionError: assert False is True
E            +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7e2ed3fe3c50>.dismiss_approval

tests/test_github_client.py:828: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:53:00.254051Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: name 'CosmicRayTestingException' is not defined [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "name 'CosmicRayTestingException' is not defined"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': "name 'CosmicRayTestingException' is not defined"}, 'event': "Failed to dismiss approval for PR #42 in owner/repo: name 'CosmicRayTestingException' is not defined", 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:53:00.254051Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_already_dismissed_is_success - AssertionError: assert False is True
 +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7e2ed3fe3c50>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected, 1 warning in 1.43s
operator: core/ExceptionReplacer, occurrence: 9, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -619,7 +619,7 @@
                 )
                 return True
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                 github_api_requests_total.labels(operation="dismiss", status="failure").inc()
........................................................................ [ 15%]
...................................................................F
=================================== FAILURES ===================================
_ TestDismissApproval.test_dismiss_approval_422_with_review_still_approved_fails _

self = <stampbot.github_client.GitHubAppClient object at 0x7503325e7b10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
>                   review.dismiss(message)

stampbot/github_client.py:582: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull().get_review().dismiss' id='128656585381200'>
args = ('Duplicate',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 422 {"message": "Validation Failed"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestDismissApproval object at 0x75033585d350>

    def test_dismiss_approval_422_with_review_still_approved_fails(self):
        """Test a 422 that leaves the review approved is reported as a failure."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "APPROVED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Validation Failed"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is False
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:864: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7503325e7b10>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
review_id = 789, message = 'Duplicate'

    def dismiss_approval(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        review_id: int,
        message: str = "Approval dismissed by Stampbot",
    ) -> bool:
        """Dismiss a pull request approval.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            review_id: Review ID to dismiss
            message: Dismissal message
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.dismiss_approval",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.review_id": review_id,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                # Get the review and dismiss it
                review = pr.get_review(review_id)
                try:
                    review.dismiss(message)
                except GithubException as e:
                    # GitHub answers 422 when a review is already dismissed,
                    # which two replicas cleaning up the same duplicate reach on
                    # purpose. 422 also covers other validation failures, so the
                    # review's state is read back before treating it as done.
                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
                        raise
                    logger.info(
                        "Review %d on PR #%d in %s was already dismissed",
                        review_id,
                        pr_number,
                        repo_full_name,
                        extra={
                            "repo": repo_full_name,
                            "pr_number": pr_number,
                            "review_id": review_id,
                        },
                    )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="dismiss").observe(duration)
                github_api_requests_total.labels(operation="dismiss", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "dismissed"})
                set_span_ok(span)
    
                logger.info(
                    f"Dismissed approval for PR #{pr_number} in {repo_full_name}",
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                        "review_id": review_id,
                    },
                )
                return True
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:622: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_422_with_review_still_approved_fails - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 139 passed, 3 deselected, 1 warning in 1.49s
operator: core/ExceptionReplacer, occurrence: 10, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -702,7 +702,7 @@
 
                 return content.decoded_content.decode("utf-8")
 
-            except GithubException as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <stampbot.github_client.GitHubAppClient object at 0x79aa26530870>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='133771696108416'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x79aa29c495b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x79aa26530870>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'nonexistent.toml', ref = None
missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:705: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.43s
operator: core/ExceptionReplacer, occurrence: 11, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -757,7 +757,7 @@
                 )
                 raise
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 github_api_requests_total.labels(operation="get_file", status="error").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
.............F
=================================== FAILURES ===================================
____ TestGetRepoFile.test_get_repo_file_propagates_read_failures[failure2] _____

self = <stampbot.github_client.GitHubAppClient object at 0x7dc8cfd4ef30>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
>               content = _get_repo_contents(repo, file_path, ref)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:683: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='138301529294224'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               TimeoutError: GitHub read timed out

/usr/lib/python3.14/unittest/mock.py:1241: TimeoutError

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetRepoFile object at 0x7dc8d74ad490>
failure = TimeoutError('GitHub read timed out')

    @pytest.mark.parametrize(
        "failure",
        [
            GithubException(403, {"message": "Forbidden"}, None),
            GithubException(500, {"message": "Error"}, None),
            TimeoutError("GitHub read timed out"),
        ],
    )
    def test_get_repo_file_propagates_read_failures(self, failure):
        """Test non-404 policy failures reach the handler's fail-closed path."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = failure
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            with pytest.raises(type(failure), match=str(failure)):
>               client.get_repo_file(123456, "owner/repo", "stampbot.toml")

tests/test_github_client.py:1410: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7dc8cfd4ef30>
installation_id = 123456, repo_full_name = 'owner/repo'
file_path = 'stampbot.toml', ref = None, missing_repository_is_optional = False

    def get_repo_file(
        self,
        installation_id: int,
        repo_full_name: str,
        file_path: str,
        ref: str | None = None,
        *,
        missing_repository_is_optional: bool = False,
    ) -> str | None:
        """Get file content from repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            file_path: Path to file in repository
            ref: Git reference (branch, tag, commit). Defaults to default branch
            missing_repository_is_optional: Treat a repository-level 404 as not
                found. This is only for optional fallback repositories.
    
        Returns:
            File content as string, or None if not found
        """
        start_time = time.time()
        repo: Repository | None = None
    
        with create_span(
            "github.get_file",
            {
                "github.repo": repo_full_name,
                "github.file_path": file_path,
                "github.ref": ref or "default",
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                # Lazy: this builds the repository URL without a request, so
                # ``repo`` stays None only when authentication itself failed.
                repo = client.get_repo(repo_full_name)
    
                content = _get_repo_contents(repo, file_path, ref)
                if isinstance(content, list):
                    duration = time.time() - start_time
                    github_api_request_duration_seconds.labels(operation="get_file").observe(
                        duration
                    )
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
                    return None
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                github_api_requests_total.labels(operation="get_file", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "found"})
                set_span_ok(span)
    
                return content.decoded_content.decode("utf-8")
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                not_found: NotFoundKind = "unconfirmed"
                if e.status == 404 and repo is not None:
                    not_found = _classify_not_found(
                        repo, ref, optional_repo=missing_repository_is_optional
                    )
                repository_is_optionally_missing = not_found == "repository"
                file_is_confirmed_missing = not_found == "file"
                if repository_is_optionally_missing or file_is_confirmed_missing:
                    github_api_requests_total.labels(operation="get_file", status="not_found").inc()
    
                    add_span_attributes(span, {"github.result": "not_found"})
                    set_span_ok(span)
    
                    if repository_is_optionally_missing:
                        logger.debug(
                            "Optional policy repository %s was not found",
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    else:
                        logger.debug(
                            "Policy file %s was not found in %s",
                            file_path,
                            repo_full_name,
                            extra={
                                "repo": repo_full_name,
                                "file_path": file_path,
                                "installation_id": installation_id,
                            },
                        )
                    return None
    
                github_api_requests_total.labels(operation="get_file", status="error").inc()
                add_span_attributes(span, {"github.result": "error"})
                set_span_error(span, e)
                logger.warning(
                    "GitHub could not read %s from %s (status %s)",
                    file_path,
                    repo_full_name,
                    e.status,
                    extra={
                        "repo": repo_full_name,
                        "file_path": file_path,
                        "installation_id": installation_id,
                    },
                )
                raise
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:760: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_propagates_read_failures[failure2] - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 157 passed, 3 deselected, 1 warning in 1.50s
operator: core/ExceptionReplacer, occurrence: 12, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -839,7 +839,7 @@
 
                 return bot_reviews
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                     duration
........................................................................ [ 15%]
........................................................................ [ 30%]
...............F
=================================== FAILURES ===================================
_ TestFindBotApprovalReviews.test_find_bot_approval_reviews_returns_none_on_error _

self = <stampbot.github_client.GitHubAppClient object at 0x7e3310eb7930>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
>               repo = client.get_repo(repo_full_name)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:809: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo' id='138757818519904'>
args = ('owner/repo',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x7e331437b610>

    def test_find_bot_approval_reviews_returns_none_on_error(self):
        """Test that a listing failure is reported as None, distinct from no reviews."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_repo.side_effect = Exception("API Error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1523: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e3310eb7930>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42

    def find_bot_approval_reviews(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
    ) -> list[BotReview] | None:
        """Find approval review states created by this bot on a PR.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
    
        Returns:
            Review state details for bot approval reviews, or None when GitHub
            could not be read. Callers decide what an unknown state means for
            them; an empty list always means "no Stampbot approvals".
        """
        start_time = time.time()
    
        with create_span(
            "github.find_bot_approval_reviews",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
                bot_user = self._bot_login()
    
                bot_reviews: list[BotReview] = []
                for review in pr.get_reviews():
                    if review.user.login == bot_user and review.state in (
                        "APPROVED",
                        "DISMISSED",
                    ):
                        bot_reviews.append(
                            {
                                "id": review.id,
                                "state": review.state,
                                "commit_id": getattr(review, "commit_id", None),
                            }
                        )
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="find_reviews").observe(
                    duration
                )
                github_api_requests_total.labels(operation="find_reviews", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span, {"github.reviews_found": len(bot_reviews), "github.bot_user": bot_user}
                )
                set_span_ok(span)
    
                return bot_reviews
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:842: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_returns_none_on_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 159 passed, 3 deselected, 1 warning in 1.49s
operator: core/ExceptionReplacer, occurrence: 13, definition_name: create_pr_review_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -918,7 +918,7 @@
                 )
                 return True
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                 github_api_requests_total.labels(operation="comment", status="failure").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
..............................F
=================================== FAILURES ===================================
_______ TestCreatePrReviewComment.test_create_pr_review_comment_failure ________

self = <stampbot.github_client.GitHubAppClient object at 0x7a3222201450>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               pr = repo.get_pull(pr_number)
                     ^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:896: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_pull' id='134355739112320'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreatePrReviewComment object at 0x7a3223d80050>

    def test_create_pr_review_comment_failure(self):
        """Test review comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_pull.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_pr_review_comment(
                123456,
                "owner/repo",
                42,
                "Config error",
            )

tests/test_github_client.py:2133: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7a3222201450>
installation_id = 123456, repo_full_name = 'owner/repo', pr_number = 42
message = 'Config error'

    def create_pr_review_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        pr_number: int,
        message: str,
    ) -> bool:
        """Create a comment review on a pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            pr_number: Pull request number
            message: Review comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_review_comment",
            {
                "github.repo": repo_full_name,
                "github.pr_number": pr_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                pr = repo.get_pull(pr_number)
    
                pr.create_review(body=message, event="COMMENT")
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="comment").observe(duration)
                github_api_requests_total.labels(operation="comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted config error review on PR #%d in %s",
                    pr_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "pr_number": pr_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:921: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreatePrReviewComment::test_create_pr_review_comment_failure - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 174 passed, 3 deselected, 1 warning in 1.50s
operator: core/ExceptionReplacer, occurrence: 14, definition_name: create_issue_comment
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -997,7 +997,7 @@
                 )
                 return True
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                     duration
........................................................................ [ 15%]
........................................................................ [ 30%]
................................F
=================================== FAILURES ===================================
___________ TestCreateIssueComment.test_create_issue_comment_failure ___________

self = <stampbot.github_client.GitHubAppClient object at 0x755df0a319f0>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
>               issue = repo.get_issue(issue_number)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:973: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_issue' id='129046329210064'>
args = (42,), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestCreateIssueComment object at 0x755df26982d0>

    def test_create_issue_comment_failure(self):
        """Test issue comment creation handles errors."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_issue.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.create_issue_comment(
                123456,
                "owner/repo",
                42,
                "Help text",
            )

tests/test_github_client.py:2223: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x755df0a319f0>
installation_id = 123456, repo_full_name = 'owner/repo', issue_number = 42
message = 'Help text'

    def create_issue_comment(
        self,
        installation_id: int,
        repo_full_name: str,
        issue_number: int,
        message: str,
    ) -> bool:
        """Create a comment on an issue or pull request.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            issue_number: Issue or pull request number
            message: Comment body
    
        Returns:
            True if successful, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.create_issue_comment",
            {
                "github.repo": repo_full_name,
                "github.issue_number": issue_number,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                issue = repo.get_issue(issue_number)
    
                issue.create_comment(message)
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="issue_comment").observe(
                    duration
                )
                github_api_requests_total.labels(operation="issue_comment", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.result": "commented"})
                set_span_ok(span)
    
                logger.info(
                    "Posted issue comment on #%d in %s",
                    issue_number,
                    repo_full_name,
                    extra={
                        "repo": repo_full_name,
                        "issue_number": issue_number,
                        "installation_id": installation_id,
                    },
                )
                return True
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:1000: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestCreateIssueComment::test_create_issue_comment_failure - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 176 passed, 3 deselected, 1 warning in 1.55s
operator: core/ExceptionReplacer, occurrence: 15, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1062,7 +1062,7 @@
                 set_span_ok(span)
                 return True
 
-            except GithubException as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <stampbot.github_client.GitHubAppClient object at 0x701811424c30>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='123248671866032'>
args = ('missing',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1241: GithubException

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x701813133d90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "missing")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1975: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x701811424c30>
installation_id = 123456, repo_full_name = 'owner/repo', label_name = 'missing'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:1065: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.49s
operator: core/ExceptionReplacer, occurrence: 16, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1087,7 +1087,7 @@
                 )
                 return None
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
........................................................................ [ 15%]
........................................................................ [ 30%]
............................F
=================================== FAILURES ===================================
__________________ TestRepoHasLabel.test_repo_has_label_error __________________

self = <stampbot.github_client.GitHubAppClient object at 0x7855ae22cb90>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
>               repo.get_label(label_name).complete()
                ^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1053: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_label' id='132309388063280'>
args = ('autoapprove',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestRepoHasLabel object at 0x7855b004de00>

    def test_repo_has_label_error(self):
        """Test repo_has_label returns None on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.repo_has_label(123456, "owner/repo", "autoapprove")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:2049: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7855ae22cb90>
installation_id = 123456, repo_full_name = 'owner/repo'
label_name = 'autoapprove'

    def repo_has_label(
        self,
        installation_id: int,
        repo_full_name: str,
        label_name: str,
    ) -> bool | None:
        """Check whether a repository has a label.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            label_name: Label name to look up
    
        Returns:
            True if label exists, False if not found, None if unknown due to error
        """
        start_time = time.time()
    
        with create_span(
            "github.get_label",
            {
                "github.repo": repo_full_name,
                "github.label": label_name,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
                # The lazy client defers the request until the label is read.
                repo.get_label(label_name).complete()
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(span, {"github.label_found": True})
                set_span_ok(span)
                return True
    
            except GithubException as e:
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                github_api_requests_total.labels(operation="get_label", status="failure").inc()
    
                if e.status == 404:
                    add_span_attributes(span, {"github.label_found": False})
                    set_span_ok(span)
                    return False
    
                set_span_error(span, e)
                logger.debug(
                    "Could not verify label %s in %s: %s",
                    label_name,
                    repo_full_name,
                    _sanitize_error(e),
                    extra={
                        "repo": repo_full_name,
                        "label": label_name,
                        "installation_id": installation_id,
                        "error": _sanitize_error(e),
                    },
                )
                return None
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:1090: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 172 passed, 3 deselected, 1 warning in 1.53s
operator: core/ExceptionReplacer, occurrence: 17, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1145,7 +1145,7 @@
                 try:
                     permission_index = permission_order.index(permission)
                     required_index = permission_order.index(required_permission)
-                except ValueError:
+                except CosmicRayTestingException:
                     permission_index = -1
                     required_index = 99
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.31s
operator: core/ExceptionReplacer, occurrence: 18, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1170,7 +1170,7 @@
 
                 return has_permission
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_permission").observe(
                     duration
........................................................................ [ 15%]
........................................................................ [ 30%]
..................F
=================================== FAILURES ===================================
_____________ TestUserHasPermission.test_user_has_permission_error _____________

self = <stampbot.github_client.GitHubAppClient object at 0x7a46b6207bb0>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
>               permission = repo.get_collaborator_permission(username)
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1143: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_collaborator_permission' id='134444150139808'>
args = ('carol',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API Error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestUserHasPermission object at 0x7a46b9955810>

    def test_user_has_permission_error(self):
        """Test permission check returns False on error."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_collaborator_permission.side_effect = Exception("API Error")
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.user_has_permission(123456, "owner/repo", "carol", "maintain")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1637: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7a46b6207bb0>
installation_id = 123456, repo_full_name = 'owner/repo', username = 'carol'
required_permission = 'maintain'

    def user_has_permission(
        self,
        installation_id: int,
        repo_full_name: str,
        username: str,
        required_permission: str,
    ) -> bool:
        """Check whether a user has required access to a repository.
    
        Args:
            installation_id: GitHub App installation ID
            repo_full_name: Repository full name (owner/repo)
            username: GitHub username to check
            required_permission: Minimum required repository permission
    
        Returns:
            True if user meets or exceeds required permission, False otherwise
        """
        start_time = time.time()
    
        with create_span(
            "github.get_collaborator_permission",
            {
                "github.repo": repo_full_name,
                "github.username": username,
                "github.required_permission": required_permission,
                "github.installation_id": installation_id,
            },
        ) as span:
            try:
                client = self._get_installation_client(installation_id)
                repo = client.get_repo(repo_full_name)
    
                permission = repo.get_collaborator_permission(username)
                permission_order = ["none", "read", "triage", "write", "maintain", "admin"]
                try:
                    permission_index = permission_order.index(permission)
                    required_index = permission_order.index(required_permission)
                except ValueError:
                    permission_index = -1
                    required_index = 99
    
                has_permission = permission_index >= required_index
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_permission").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_permission", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.permission": permission,
                        "github.has_permission": has_permission,
                    },
                )
                set_span_ok(span)
    
                return has_permission
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:1173: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestUserHasPermission::test_user_has_permission_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 162 passed, 3 deselected, 1 warning in 1.52s
operator: core/ExceptionReplacer, occurrence: 19, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1248,7 +1248,7 @@
                                     "team": team_slug,
                                 },
                             )
-                    except GithubException as e:
+                    except CosmicRayTestingException as e:
                         # Team not found or no access - skip silently
                         logger.debug(
                             "Could not check team %s membership: %s",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ExceptionReplacer, occurrence: 20, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1281,7 +1281,7 @@
 
                 return member_teams
 
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                     duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................F
=================================== FAILURES ===================================
_______ TestGetUserTeamSlugs.test_get_user_team_slugs_general_exception ________

self = <stampbot.github_client.GitHubAppClient object at 0x78c382a38870>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
>               org = client.get_organization(org_name)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/github_client.py:1229: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization' id='132781106495568'>
args = ('acme',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: Network error

/usr/lib/python3.14/unittest/mock.py:1241: Exception

During handling of the above exception, another exception occurred:

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x78c38479f530>

    def test_get_user_team_slugs_general_exception(self):
        """Test team membership check with general exception."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_github = Mock()
            mock_github.get_organization.side_effect = Exception("Network error")
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_user_team_slugs(123456, "acme", "alice", ["release-team"])
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1893: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x78c382a38870>
installation_id = 123456, org_name = 'acme', username = 'alice'
allowed_teams = ['release-team']

    def get_user_team_slugs(
        self,
        installation_id: int,
        org_name: str,
        username: str,
        allowed_teams: list[str],
    ) -> list[str]:
        """Get team slugs the user is a member of from the allowed teams list.
    
        Args:
            installation_id: GitHub App installation ID
            org_name: Organization name
            username: GitHub username to check
            allowed_teams: List of team slugs to check (can be "org/team" or just "team")
    
        Returns:
            List of team slugs the user is a member of
        """
        start_time = time.time()
    
        with create_span(
            "github.get_user_team_slugs",
            {
                "github.org": org_name,
                "github.username": username,
                "github.installation_id": installation_id,
                "github.teams_to_check": len(allowed_teams),
            },
        ) as span:
            member_teams: list[str] = []
    
            try:
                client = self._get_installation_client(installation_id)
                org = client.get_organization(org_name)
                user = client.get_user(username)
    
                for team_ref in allowed_teams:
                    # Extract team slug (handle both "org/team" and "team" formats)
                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
    
                    try:
                        team = org.get_team_by_slug(team_slug)
                        # Check if user is a member
                        if team.has_in_members(user):  # type: ignore[arg-type]
                            member_teams.append(team_slug)
                            logger.debug(
                                "User %s is a member of team %s",
                                username,
                                team_slug,
                                extra={
                                    "org": org_name,
                                    "username": username,
                                    "team": team_slug,
                                },
                            )
                    except GithubException as e:
                        # Team not found or no access - skip silently
                        logger.debug(
                            "Could not check team %s membership: %s",
                            team_slug,
                            e.data.get("message", str(e)) if hasattr(e, "data") else str(e),
                            extra={
                                "org": org_name,
                                "team": team_slug,
                                "username": username,
                            },
                        )
                        continue
    
                duration = time.time() - start_time
                github_api_request_duration_seconds.labels(operation="get_user_teams").observe(
                    duration
                )
                github_api_requests_total.labels(operation="get_user_teams", status="success").inc()
    
                self._update_rate_limit_metrics(client, installation_id)
    
                add_span_attributes(
                    span,
                    {
                        "github.member_teams": len(member_teams),
                        "github.teams_checked": len(allowed_teams),
                    },
                )
                set_span_ok(span)
    
                return member_teams
    
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/github_client.py:1284: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_general_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 168 passed, 3 deselected, 1 warning in 1.51s
operator: core/NumberReplacer, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -30,7 +30,7 @@
 from stampbot.telemetry import add_span_attributes, create_span, set_span_error, set_span_ok
 
 # GitHub API configuration
-GITHUB_API_TIMEOUT = 30  # seconds
+GITHUB_API_TIMEOUT = 31  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
 MAX_PRIVATE_KEY_SIZE = 64 * 1024
........................................................................ [ 15%]
....................................................F
=================================== FAILURES ===================================
_ TestGetInstallationClient.test_get_installation_client_reuses_credentials_per_installation _

self = <tests.test_github_client.TestGetInstallationClient object at 0x71c1c9fc8fc0>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='125077079127584'>

    def test_get_installation_client_reuses_credentials_per_installation(
        self, installation_auth_cls
    ):
        """Test credentials are shared per installation while each call gets its own client."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            mock_github_cls.side_effect = lambda **kwargs: Mock(name="github", init_kwargs=kwargs)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            first = client._get_installation_client(111)
            second = client._get_installation_client(111)
            other = client._get_installation_client(222)
    
            # A PyGithub client keeps in-flight request state on its connection,
            # so every call gets a private client bound to shared credentials.
            assert first is not second
            assert other is not first
            # Three calls build one client each; credentials are created once per installation.
            assert mock_github_cls.call_count == 3
            assert installation_auth_cls.call_args_list == [
                call(mock_auth_cls.return_value, 111),
                call(mock_auth_cls.return_value, 222),
            ]
            kwargs = mock_github_cls.call_args_list[0].kwargs
            assert kwargs["lazy"] is True
>           assert kwargs["timeout"] == 30
E           assert 31 == 30

tests/test_github_client.py:398: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_reuses_credentials_per_installation - assert 31 == 30
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 124 passed, 3 deselected, 1 warning in 1.40s
operator: core/NumberReplacer, occurrence: 1, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -30,7 +30,7 @@
 from stampbot.telemetry import add_span_attributes, create_span, set_span_error, set_span_ok
 
 # GitHub API configuration
-GITHUB_API_TIMEOUT = 30  # seconds
+GITHUB_API_TIMEOUT = 29  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
 MAX_PRIVATE_KEY_SIZE = 64 * 1024
........................................................................ [ 15%]
....................................................F
=================================== FAILURES ===================================
_ TestGetInstallationClient.test_get_installation_client_reuses_credentials_per_installation _

self = <tests.test_github_client.TestGetInstallationClient object at 0x7e894e648fc0>
installation_auth_cls = <MagicMock name='InstrumentedInstallationAuth' id='139128131909152'>

    def test_get_installation_client_reuses_credentials_per_installation(
        self, installation_auth_cls
    ):
        """Test credentials are shared per installation while each call gets its own client."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth_cls,
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            mock_github_cls.side_effect = lambda **kwargs: Mock(name="github", init_kwargs=kwargs)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            first = client._get_installation_client(111)
            second = client._get_installation_client(111)
            other = client._get_installation_client(222)
    
            # A PyGithub client keeps in-flight request state on its connection,
            # so every call gets a private client bound to shared credentials.
            assert first is not second
            assert other is not first
            # Three calls build one client each; credentials are created once per installation.
            assert mock_github_cls.call_count == 3
            assert installation_auth_cls.call_args_list == [
                call(mock_auth_cls.return_value, 111),
                call(mock_auth_cls.return_value, 222),
            ]
            kwargs = mock_github_cls.call_args_list[0].kwargs
            assert kwargs["lazy"] is True
>           assert kwargs["timeout"] == 30
E           assert 29 == 30

tests/test_github_client.py:398: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetInstallationClient::test_get_installation_client_reuses_credentials_per_installation - assert 29 == 30
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 124 passed, 3 deselected, 1 warning in 1.40s
operator: core/NumberReplacer, occurrence: 2, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -31,7 +31,7 @@
 
 # GitHub API configuration
 GITHUB_API_TIMEOUT = 30  # seconds
-GITHUB_API_RETRY_TOTAL = 3
+GITHUB_API_RETRY_TOTAL = 4
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
 MAX_PRIVATE_KEY_SIZE = 64 * 1024
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 3, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -31,7 +31,7 @@
 
 # GitHub API configuration
 GITHUB_API_TIMEOUT = 30  # seconds
-GITHUB_API_RETRY_TOTAL = 3
+GITHUB_API_RETRY_TOTAL = 2
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
 MAX_PRIVATE_KEY_SIZE = 64 * 1024
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/NumberReplacer, occurrence: 4, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -32,7 +32,7 @@
 # GitHub API configuration
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
-GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
+GITHUB_API_RETRY_BACKOFF = 1.5  # exponential backoff factor
 MAX_PRIVATE_KEY_SIZE = 64 * 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/NumberReplacer, occurrence: 5, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -32,7 +32,7 @@
 # GitHub API configuration
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
-GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
+GITHUB_API_RETRY_BACKOFF = -0.5  # exponential backoff factor
 MAX_PRIVATE_KEY_SIZE = 64 * 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/NumberReplacer, occurrence: 6, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 65 * 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/NumberReplacer, occurrence: 7, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 63 * 1024
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 8, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 * 1025
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/NumberReplacer, occurrence: 9, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -33,7 +33,7 @@
 GITHUB_API_TIMEOUT = 30  # seconds
 GITHUB_API_RETRY_TOTAL = 3
 GITHUB_API_RETRY_BACKOFF = 0.5  # exponential backoff factor
-MAX_PRIVATE_KEY_SIZE = 64 * 1024
+MAX_PRIVATE_KEY_SIZE = 64 * 1023
 
 # Installation credentials hold a token that PyGithub refreshes shortly before
 # GitHub expires it. Entries expire an hour after creation, which bounds memory
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/NumberReplacer, occurrence: 10, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -40,7 +40,7 @@
 # for installations that disappear and rotates the token about once an hour.
 # The size also bounds the installation_id label on the rate-limit gauges, which
 # keeps those metrics inside the repository's low-cardinality rule.
-INSTALLATION_AUTH_CACHE_SIZE = 100
+INSTALLATION_AUTH_CACHE_SIZE = 101
 INSTALLATION_AUTH_CACHE_TTL = 3600  # seconds
 # The App slug only changes when an operator renames the App.
 BOT_LOGIN_TTL = 3600  # seconds
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 11, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -40,7 +40,7 @@
 # for installations that disappear and rotates the token about once an hour.
 # The size also bounds the installation_id label on the rate-limit gauges, which
 # keeps those metrics inside the repository's low-cardinality rule.
-INSTALLATION_AUTH_CACHE_SIZE = 100
+INSTALLATION_AUTH_CACHE_SIZE = 99
 INSTALLATION_AUTH_CACHE_TTL = 3600  # seconds
 # The App slug only changes when an operator renames the App.
 BOT_LOGIN_TTL = 3600  # seconds
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/NumberReplacer, occurrence: 12, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -41,7 +41,7 @@
 # The size also bounds the installation_id label on the rate-limit gauges, which
 # keeps those metrics inside the repository's low-cardinality rule.
 INSTALLATION_AUTH_CACHE_SIZE = 100
-INSTALLATION_AUTH_CACHE_TTL = 3600  # seconds
+INSTALLATION_AUTH_CACHE_TTL = 3601  # seconds
 # The App slug only changes when an operator renames the App.
 BOT_LOGIN_TTL = 3600  # seconds
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 13, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -41,7 +41,7 @@
 # The size also bounds the installation_id label on the rate-limit gauges, which
 # keeps those metrics inside the repository's low-cardinality rule.
 INSTALLATION_AUTH_CACHE_SIZE = 100
-INSTALLATION_AUTH_CACHE_TTL = 3600  # seconds
+INSTALLATION_AUTH_CACHE_TTL = 3599  # seconds
 # The App slug only changes when an operator renames the App.
 BOT_LOGIN_TTL = 3600  # seconds
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/NumberReplacer, occurrence: 14, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -43,7 +43,7 @@
 INSTALLATION_AUTH_CACHE_SIZE = 100
 INSTALLATION_AUTH_CACHE_TTL = 3600  # seconds
 # The App slug only changes when an operator renames the App.
-BOT_LOGIN_TTL = 3600  # seconds
+BOT_LOGIN_TTL = 3601  # seconds
 
 logger = get_logger(__name__)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 15, definition_name: None
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -43,7 +43,7 @@
 INSTALLATION_AUTH_CACHE_SIZE = 100
 INSTALLATION_AUTH_CACHE_TTL = 3600  # seconds
 # The App slug only changes when an operator renames the App.
-BOT_LOGIN_TTL = 3600  # seconds
+BOT_LOGIN_TTL = 3599  # seconds
 
 logger = get_logger(__name__)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 16, definition_name: _repository_is_missing
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -102,7 +102,7 @@
     try:
         repo.complete()
     except GithubException as e:
-        return e.status == 404
+        return e.status == 405
     except Exception:
         return False
     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
.......F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_accepts_missing_optional_repository_with_lazy_client _

self = <tests.test_github_client.TestGetRepoFile object at 0x7c89d5210d70>

    def test_get_repo_file_accepts_missing_optional_repository_with_lazy_client(self):
        """Test a lazy client confirms the optional repository itself is missing."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_integration_cls.return_value = mock_integration
    
            # Lazy get_repo never raises. The file read and the root read both
            # 404, and only the repository request proves the repo is absent.
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                GithubException(404, {"message": "Not Found"}, None),
            ]
            mock_repo.complete.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/.github",
                "stampbot.toml",
                missing_repository_is_optional=True,
            )

tests/test_github_client.py:1236: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='136931372640512'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:56:40.249756Z [warning  ] GitHub could not read stampbot.toml from owner/.github (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/.github (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:56:40.249756Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_accepts_missing_optional_repository_with_lazy_client - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 151 passed, 3 deselected, 1 warning in 1.45s
operator: core/NumberReplacer, occurrence: 17, definition_name: _repository_is_missing
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -102,7 +102,7 @@
     try:
         repo.complete()
     except GithubException as e:
-        return e.status == 404
+        return e.status == 403
     except Exception:
         return False
     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
.......F
=================================== FAILURES ===================================
_ TestGetRepoFile.test_get_repo_file_accepts_missing_optional_repository_with_lazy_client _

self = <tests.test_github_client.TestGetRepoFile object at 0x746bf0bb8d70>

    def test_get_repo_file_accepts_missing_optional_repository_with_lazy_client(self):
        """Test a lazy client confirms the optional repository itself is missing."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_integration_cls.return_value = mock_integration
    
            # Lazy get_repo never raises. The file read and the root read both
            # 404, and only the repository request proves the repo is absent.
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                GithubException(404, {"message": "Not Found"}, None),
            ]
            mock_repo.complete.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(
                123456,
                "owner/.github",
                "stampbot.toml",
                missing_repository_is_optional=True,
            )

tests/test_github_client.py:1236: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='128006895069440'>
args = ('stampbot.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:47:38.449190Z [warning  ] GitHub could not read stampbot.toml from owner/.github (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/.github', 'file_path': 'stampbot.toml', 'installation_id': 123456}, 'event': 'GitHub could not read stampbot.toml from owner/.github (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:47:38.449190Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_accepts_missing_optional_repository_with_lazy_client - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 151 passed, 3 deselected, 1 warning in 1.52s
operator: core/NumberReplacer, occurrence: 18, definition_name: _build_retry
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -131,7 +131,7 @@
     return Retry(
         total=GITHUB_API_RETRY_TOTAL,
         backoff_factor=GITHUB_API_RETRY_BACKOFF,
-        status_forcelist=[500, 502, 503, 504],
+        status_forcelist=[ 501, 502, 503, 504],
         allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 19, definition_name: _build_retry
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -131,7 +131,7 @@
     return Retry(
         total=GITHUB_API_RETRY_TOTAL,
         backoff_factor=GITHUB_API_RETRY_BACKOFF,
-        status_forcelist=[500, 502, 503, 504],
+        status_forcelist=[ 499, 502, 503, 504],
         allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 20, definition_name: _build_retry
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -131,7 +131,7 @@
     return Retry(
         total=GITHUB_API_RETRY_TOTAL,
         backoff_factor=GITHUB_API_RETRY_BACKOFF,
-        status_forcelist=[500, 502, 503, 504],
+        status_forcelist=[500, 503, 503, 504],
         allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/NumberReplacer, occurrence: 21, definition_name: _build_retry
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -131,7 +131,7 @@
     return Retry(
         total=GITHUB_API_RETRY_TOTAL,
         backoff_factor=GITHUB_API_RETRY_BACKOFF,
-        status_forcelist=[500, 502, 503, 504],
+        status_forcelist=[500, 501, 503, 504],
         allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/NumberReplacer, occurrence: 22, definition_name: _build_retry
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -131,7 +131,7 @@
     return Retry(
         total=GITHUB_API_RETRY_TOTAL,
         backoff_factor=GITHUB_API_RETRY_BACKOFF,
-        status_forcelist=[500, 502, 503, 504],
+        status_forcelist=[500, 502, 504, 504],
         allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/NumberReplacer, occurrence: 23, definition_name: _build_retry
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -131,7 +131,7 @@
     return Retry(
         total=GITHUB_API_RETRY_TOTAL,
         backoff_factor=GITHUB_API_RETRY_BACKOFF,
-        status_forcelist=[500, 502, 503, 504],
+        status_forcelist=[500, 502, 502, 504],
         allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/NumberReplacer, occurrence: 24, definition_name: _build_retry
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -131,7 +131,7 @@
     return Retry(
         total=GITHUB_API_RETRY_TOTAL,
         backoff_factor=GITHUB_API_RETRY_BACKOFF,
-        status_forcelist=[500, 502, 503, 504],
+        status_forcelist=[500, 502, 503, 505],
         allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/NumberReplacer, occurrence: 25, definition_name: _build_retry
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -131,7 +131,7 @@
     return Retry(
         total=GITHUB_API_RETRY_TOTAL,
         backoff_factor=GITHUB_API_RETRY_BACKOFF,
-        status_forcelist=[500, 502, 503, 504],
+        status_forcelist=[500, 502, 503, 503],
         allowed_methods=["GET", "POST", "PUT", "DELETE", "PATCH"],
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.30s
operator: core/NumberReplacer, occurrence: 26, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 2)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 27, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -238,7 +238,7 @@
                         raise ValueError("Private key path must reference a regular file")
                     if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                         raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
-                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
+                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 0)
             except FileNotFoundError:
                 raise ValueError("Invalid private key path: file does not exist") from None
             except OSError as e:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/NumberReplacer, occurrence: 28, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if len(pem_lines) < 4 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x744cba9ebad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x744cb774c9e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 4 or not pem_lines[0].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.41s
operator: core/NumberReplacer, occurrence: 29, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if len(pem_lines) < 2 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/NumberReplacer, occurrence: 30, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if len(pem_lines) < 3 or not pem_lines[ 1].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7b02c39dbad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7b02c07489e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[ 1].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.38s
operator: core/NumberReplacer, occurrence: 31, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -251,7 +251,7 @@
         # Validate a complete PEM private-key envelope. PyGithub performs the
         # cryptographic parsing when it creates AppAuth.
         pem_lines = pem_content.strip().splitlines()
-        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
+        if len(pem_lines) < 3 or not pem_lines[ -1].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x74674d8ebad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x74674a0b49e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[ -1].startswith("-----BEGIN "):
>           raise ValueError("Private key must be in PEM format")
E           ValueError: Private key must be in PEM format

stampbot/github_client.py:255: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key must be in PEM format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/NumberReplacer, occurrence: 32, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -253,7 +253,7 @@
         pem_lines = pem_content.strip().splitlines()
         if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
-        if not pem_lines[0].endswith("PRIVATE KEY-----"):
+        if not pem_lines[ 1].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7e05ca7ebad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7e05c75449e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[ 1].endswith("PRIVATE KEY-----"):
>           raise ValueError("PEM value must contain a private key")
E           ValueError: PEM value must contain a private key

stampbot/github_client.py:257: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: PEM value must contain a private key
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.44s
operator: core/NumberReplacer, occurrence: 33, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -253,7 +253,7 @@
         pem_lines = pem_content.strip().splitlines()
         if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
             raise ValueError("Private key must be in PEM format")
-        if not pem_lines[0].endswith("PRIVATE KEY-----"):
+        if not pem_lines[ -1].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.42s
operator: core/NumberReplacer, occurrence: 34, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -255,7 +255,7 @@
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
-        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
+        expected_footer = pem_lines[ 1].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x744b1dac7ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x744b1a83c9e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[ 1].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] != expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.41s
operator: core/NumberReplacer, occurrence: 35, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -255,7 +255,7 @@
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
-        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
+        expected_footer = pem_lines[ -1].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
........................................................................ [ 15%]
..............................................F
=================================== FAILURES ===================================
______ TestLoadPrivateKey.test_load_private_key_rejects_mismatched_footer ______

self = <tests.test_github_client.TestLoadPrivateKey object at 0x729f3b4eaf30>

    def test_load_private_key_rejects_mismatched_footer(self):
        """Test the PEM header and footer must describe the same key type."""
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = (
                "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----"
            )
    
            from stampbot.github_client import GitHubAppClient
    
>           with pytest.raises(ValueError, match="footer does not match"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE ValueError

tests/test_github_client.py:271: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_rejects_mismatched_footer - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 118 passed, 3 deselected, 1 warning in 1.41s
operator: core/NumberReplacer, occurrence: 36, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -255,7 +255,7 @@
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
-        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
+        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 2)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 37, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -255,7 +255,7 @@
             raise ValueError("Private key must be in PEM format")
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
-        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
+        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 0)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7c5d3d6dbad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7c5d3a4449e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 0)
        if pem_lines[-1] != expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/NumberReplacer, occurrence: 38, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[- 2] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x756964983ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7569617389e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[- 2] != expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.37s
operator: core/NumberReplacer, occurrence: 39, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -256,7 +256,7 @@
         if not pem_lines[0].endswith("PRIVATE KEY-----"):
             raise ValueError("PEM value must contain a private key")
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
-        if pem_lines[-1] != expected_footer:
+        if pem_lines[- 0] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
         if not any(line.strip() for line in pem_lines[1:-1]):
             raise ValueError("Private key PEM body is empty")
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x748c7e58bad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x748c7b2e89e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[- 0] != expected_footer:
>           raise ValueError("Private key PEM footer does not match its header")
E           ValueError: Private key PEM footer does not match its header

stampbot/github_client.py:260: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM footer does not match its header
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.40s
operator: core/NumberReplacer, occurrence: 40, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -258,7 +258,7 @@
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
-        if not any(line.strip() for line in pem_lines[1:-1]):
+        if not any(line.strip() for line in pem_lines[ 2:-1]):
             raise ValueError("Private key PEM body is empty")
 
         return pem_content
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7cdc7ecdfad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7cdc7b4a49e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] != expected_footer:
            raise ValueError("Private key PEM footer does not match its header")
        if not any(line.strip() for line in pem_lines[ 2:-1]):
>           raise ValueError("Private key PEM body is empty")
E           ValueError: Private key PEM body is empty

stampbot/github_client.py:262: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM body is empty
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.41s
operator: core/NumberReplacer, occurrence: 41, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -258,7 +258,7 @@
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
-        if not any(line.strip() for line in pem_lines[1:-1]):
+        if not any(line.strip() for line in pem_lines[ 0:-1]):
             raise ValueError("Private key PEM body is empty")
 
         return pem_content
........................................................................ [ 15%]
.................................................F
=================================== FAILURES ===================================
_______ TestLoadPrivateKey.test_load_private_key_rejects_empty_pem_body ________

self = <tests.test_github_client.TestLoadPrivateKey object at 0x7c58efa5ec10>

    def test_load_private_key_rejects_empty_pem_body(self):
        """Test a private-key envelope must contain body data."""
        with patch("stampbot.github_client.settings") as mock_settings:
            mock_settings.private_key = TEST_PEM_KEY.replace("test", "")
    
            from stampbot.github_client import GitHubAppClient
    
>           with pytest.raises(ValueError, match="body is empty"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE ValueError

tests/test_github_client.py:303: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestLoadPrivateKey::test_load_private_key_rejects_empty_pem_body - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 121 passed, 3 deselected, 1 warning in 1.40s
operator: core/NumberReplacer, occurrence: 42, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -258,7 +258,7 @@
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
-        if not any(line.strip() for line in pem_lines[1:-1]):
+        if not any(line.strip() for line in pem_lines[1:- 2]):
             raise ValueError("Private key PEM body is empty")
 
         return pem_content
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x7166b46bbad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7166b13e89e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] != expected_footer:
            raise ValueError("Private key PEM footer does not match its header")
        if not any(line.strip() for line in pem_lines[1:- 2]):
>           raise ValueError("Private key PEM body is empty")
E           ValueError: Private key PEM body is empty

stampbot/github_client.py:262: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM body is empty
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.43s
operator: core/NumberReplacer, occurrence: 43, definition_name: _load_private_key
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -258,7 +258,7 @@
         expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
         if pem_lines[-1] != expected_footer:
             raise ValueError("Private key PEM footer does not match its header")
-        if not any(line.strip() for line in pem_lines[1:-1]):
+        if not any(line.strip() for line in pem_lines[1:- 0]):
             raise ValueError("Private key PEM body is empty")
 
         return pem_content
........................................................................ [ 15%]
...................................F
=================================== FAILURES ===================================
____________ TestEnsureInitialized.test_ensure_initialized_success _____________

self = <tests.test_github_client.TestEnsureInitialized object at 0x743657bf3ad0>

    def test_ensure_initialized_success(self):
        """Test successful initialization."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth") as mock_auth,
            patch("stampbot.github_client.GithubIntegration") as mock_integration,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           client._ensure_initialized()

tests/test_github_client.py:114: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:188: in _ensure_initialized
    private_key = self._load_private_key()
                  ^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.github_client.GitHubAppClient object at 0x7436549449e0>

    def _load_private_key(self) -> str:
        """Load private key from settings or file.
    
        Returns:
            Private key contents as PEM string.
    
        Raises:
            RuntimeError: If private key is not configured.
            ValueError: If private key is not valid PEM format.
            OSError: If private key file cannot be read.
        """
        key: str | None = settings.private_key
    
        if key is None:
            raise RuntimeError("Private key not configured")
    
        # A non-PEM value is an operator-selected file path. It is not
        # request-controlled, so arbitrary relative and absolute paths are
        # intentional. Validate the opened target instead of pretending that
        # normalizing '..' segments creates a meaningful traversal boundary.
        if key.startswith("-----BEGIN"):
            pem_content = key
        else:
            try:
                with Path(key).expanduser().open(encoding="utf-8") as key_file:
                    file_metadata = os.fstat(key_file.fileno())
                    if not stat.S_ISREG(file_metadata.st_mode):
                        raise ValueError("Private key path must reference a regular file")
                    if file_metadata.st_size > MAX_PRIVATE_KEY_SIZE:
                        raise ValueError(f"Private key file exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
                    pem_content = key_file.read(MAX_PRIVATE_KEY_SIZE + 1)
            except FileNotFoundError:
                raise ValueError("Invalid private key path: file does not exist") from None
            except OSError as e:
                logger.error("Failed to read private key file: %s", e)
                raise
    
        if len(pem_content.encode("utf-8")) > MAX_PRIVATE_KEY_SIZE:
            raise ValueError(f"Private key exceeds {MAX_PRIVATE_KEY_SIZE} bytes")
    
        # Validate a complete PEM private-key envelope. PyGithub performs the
        # cryptographic parsing when it creates AppAuth.
        pem_lines = pem_content.strip().splitlines()
        if len(pem_lines) < 3 or not pem_lines[0].startswith("-----BEGIN "):
            raise ValueError("Private key must be in PEM format")
        if not pem_lines[0].endswith("PRIVATE KEY-----"):
            raise ValueError("PEM value must contain a private key")
        expected_footer = pem_lines[0].replace("-----BEGIN ", "-----END ", 1)
        if pem_lines[-1] != expected_footer:
            raise ValueError("Private key PEM footer does not match its header")
        if not any(line.strip() for line in pem_lines[1:- 0]):
>           raise ValueError("Private key PEM body is empty")
E           ValueError: Private key PEM body is empty

stampbot/github_client.py:262: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestEnsureInitialized::test_ensure_initialized_success - ValueError: Private key PEM body is empty
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 107 passed, 3 deselected, 1 warning in 1.39s
operator: core/NumberReplacer, occurrence: 44, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining < 1 or limit < 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/NumberReplacer, occurrence: 45, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining < -1 or limit < 0:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/NumberReplacer, occurrence: 46, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining < 0 or limit < 1:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/NumberReplacer, occurrence: 47, definition_name: _update_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -370,7 +370,7 @@
         """
         try:
             remaining, limit = client.requester.rate_limiting
-            if remaining < 0 or limit < 0:
+            if remaining < 0 or limit < -1:
                 # PyGithub reports -1 until a response has carried the headers.
                 return
             label = str(installation_id)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 48, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if self._bot_login_value is not None and self._bot_login_value[ 2] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 49, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -394,7 +394,7 @@
         """
         with self._bot_login_lock:
             now = time.monotonic()
-            if self._bot_login_value is not None and self._bot_login_value[1] > now:
+            if self._bot_login_value is not None and self._bot_login_value[ 0] > now:
                 return self._bot_login_value[0]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.44s
operator: core/NumberReplacer, occurrence: 50, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -395,7 +395,7 @@
         with self._bot_login_lock:
             now = time.monotonic()
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
-                return self._bot_login_value[0]
+                return self._bot_login_value[ 1]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
             return login
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/NumberReplacer, occurrence: 51, definition_name: _bot_login
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -395,7 +395,7 @@
         with self._bot_login_lock:
             now = time.monotonic()
             if self._bot_login_value is not None and self._bot_login_value[1] > now:
-                return self._bot_login_value[0]
+                return self._bot_login_value[ -1]
             login = f"{self.integration.get_app().slug}[bot]"
             self._bot_login_value = (login, now + BOT_LOGIN_TTL)
             return login
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/NumberReplacer, occurrence: 52, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status != 423 or pr.get_review(review_id).state != "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
..................................................................F
=================================== FAILURES ===================================
____ TestDismissApproval.test_dismiss_approval_already_dismissed_is_success ____

self = <tests.test_github_client.TestDismissApproval object at 0x7c94b338f110>

    def test_dismiss_approval_already_dismissed_is_success(self):
        """Test GitHub's 422 for an already dismissed review counts as done."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "DISMISSED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Can not dismiss a dismissed pull request review"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is True
E           AssertionError: assert False is True
E            +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7c94b013fc50>.dismiss_approval

tests/test_github_client.py:828: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:41:38.943793Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"} [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}, 'event': 'Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"}', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:41:38.943793Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_already_dismissed_is_success - AssertionError: assert False is True
 +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x7c94b013fc50>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected, 1 warning in 1.41s
operator: core/NumberReplacer, occurrence: 53, definition_name: dismiss_approval
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -585,7 +585,7 @@
                     # which two replicas cleaning up the same duplicate reach on
                     # purpose. 422 also covers other validation failures, so the
                     # review's state is read back before treating it as done.
-                    if e.status != 422 or pr.get_review(review_id).state != "DISMISSED":
+                    if e.status != 421 or pr.get_review(review_id).state != "DISMISSED":
                         raise
                     logger.info(
                         "Review %d on PR #%d in %s was already dismissed",
........................................................................ [ 15%]
..................................................................F
=================================== FAILURES ===================================
____ TestDismissApproval.test_dismiss_approval_already_dismissed_is_success ____

self = <tests.test_github_client.TestDismissApproval object at 0x74daee687110>

    def test_dismiss_approval_already_dismissed_is_success(self):
        """Test GitHub's 422 for an already dismissed review counts as done."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration"),
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_review = Mock()
            mock_review.state = "DISMISSED"
            mock_review.dismiss.side_effect = GithubException(
                422, {"message": "Can not dismiss a dismissed pull request review"}, None
            )
            mock_pr = Mock()
            mock_pr.get_review.return_value = mock_review
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
    
>           assert client.dismiss_approval(123456, "owner/repo", 42, 789, "Duplicate") is True
E           AssertionError: assert False is True
E            +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
E            +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x74daeb343c50>.dismiss_approval

tests/test_github_client.py:828: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:23:30.630634Z [error    ] Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"} [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=error extra={'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.github_client:github_client.py:629 {'extra': {'repo': 'owner/repo', 'pr_number': 42, 'installation_id': 123456, 'error': '422 {"message": "Can not dismiss a dismissed pull request review"}'}, 'event': 'Failed to dismiss approval for PR #42 in owner/repo: 422 {"message": "Can not dismiss a dismissed pull request review"}', 'logger': 'stampbot.github_client', 'level': 'error', 'timestamp': '2026-11-04T03:23:30.630634Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestDismissApproval::test_dismiss_approval_already_dismissed_is_success - AssertionError: assert False is True
 +  where False = dismiss_approval(123456, 'owner/repo', 42, 789, 'Duplicate')
 +    where dismiss_approval = <stampbot.github_client.GitHubAppClient object at 0x74daeb343c50>.dismiss_approval
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 138 passed, 3 deselected, 1 warning in 1.42s
operator: core/NumberReplacer, occurrence: 54, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -706,7 +706,7 @@
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
-                if e.status == 404 and repo is not None:
+                if e.status == 405 and repo is not None:
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x72d06d45d5b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='126239455409024'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:14:44.369711Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T03:14:44.369711Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.43s
operator: core/NumberReplacer, occurrence: 55, definition_name: get_repo_file
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -706,7 +706,7 @@
                 duration = time.time() - start_time
                 github_api_request_duration_seconds.labels(operation="get_file").observe(duration)
                 not_found: NotFoundKind = "unconfirmed"
-                if e.status == 404 and repo is not None:
+                if e.status == 403 and repo is not None:
                     not_found = _classify_not_found(
                         repo, ref, optional_repo=missing_repository_is_optional
                     )
........................................................................ [ 15%]
........................................................................ [ 30%]
F
=================================== FAILURES ===================================
_________ TestGetRepoFile.test_get_repo_file_returns_none_on_not_found _________

self = <tests.test_github_client.TestGetRepoFile object at 0x734db48495b0>

    def test_get_repo_file_returns_none_on_not_found(self):
        """Test that a clean GitHub not-found result advances policy lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_contents.side_effect = [
                GithubException(404, {"message": "Not Found"}, None),
                [],
            ]
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           result = client.get_repo_file(123456, "owner/repo", "nonexistent.toml")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_github_client.py:1060: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/github_client.py:683: in get_repo_file
    content = _get_repo_contents(repo, file_path, ref)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/github_client.py:82: in _get_repo_contents
    return repo.get_contents(path)
           ^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_repo().get_contents' id='126777522353024'>
args = ('nonexistent.toml',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
                raise effect
            elif not _callable(effect):
                result = next(effect)
                if _is_exception(result):
>                   raise result
E                   github.GithubException.GithubException: 404 {"message": "Not Found"}

/usr/lib/python3.14/unittest/mock.py:1245: GithubException
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:35:05.170634Z [warning  ] GitHub could not read nonexistent.toml from owner/repo (status 404) [stampbot.github_client] _logger=<_FixedFindCallerLogger stampbot.github_client (INFO)> _name=warning extra={'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.github_client:github_client.py:747 {'extra': {'repo': 'owner/repo', 'file_path': 'nonexistent.toml', 'installation_id': 123456}, 'event': 'GitHub could not read nonexistent.toml from owner/repo (status 404)', 'logger': 'stampbot.github_client', 'level': 'warning', 'timestamp': '2026-11-04T04:35:05.170634Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetRepoFile::test_get_repo_file_returns_none_on_not_found - github.GithubException.GithubException: 404 {"message": "Not Found"}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 144 passed, 3 deselected, 1 warning in 1.47s
operator: core/NumberReplacer, occurrence: 56, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1067,7 +1067,7 @@
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
-                if e.status == 404:
+                if e.status == 405:
                     add_span_attributes(span, {"github.label_found": False})
                     set_span_ok(span)
                     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x72d4dea4fd90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "missing")
    
>           assert result is False
E           assert None is False

tests/test_github_client.py:1977: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.50s
operator: core/NumberReplacer, occurrence: 57, definition_name: repo_has_label
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1067,7 +1067,7 @@
                 github_api_request_duration_seconds.labels(operation="get_label").observe(duration)
                 github_api_requests_total.labels(operation="get_label", status="failure").inc()
 
-                if e.status == 404:
+                if e.status == 403:
                     add_span_attributes(span, {"github.label_found": False})
                     set_span_ok(span)
                     return False
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................F
=================================== FAILURES ===================================
________________ TestRepoHasLabel.test_repo_has_label_not_found ________________

self = <tests.test_github_client.TestRepoHasLabel object at 0x7d74fc397d90>

    def test_repo_has_label_not_found(self):
        """Test repo_has_label returns False when label is missing."""
        from github.GithubException import GithubException
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_repo = Mock()
            mock_repo.get_label.side_effect = GithubException(404, {"message": "Not Found"}, None)
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.repo_has_label(123456, "owner/repo", "missing")
    
>           assert result is False
E           assert None is False

tests/test_github_client.py:1977: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRepoHasLabel::test_repo_has_label_not_found - assert None is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 170 passed, 3 deselected, 1 warning in 1.45s
operator: core/NumberReplacer, occurrence: 58, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1146,7 +1146,7 @@
                     permission_index = permission_order.index(permission)
                     required_index = permission_order.index(required_permission)
                 except ValueError:
-                    permission_index = -1
+                    permission_index = - 2
                     required_index = 99
 
                 has_permission = permission_index >= required_index
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/NumberReplacer, occurrence: 59, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1146,7 +1146,7 @@
                     permission_index = permission_order.index(permission)
                     required_index = permission_order.index(required_permission)
                 except ValueError:
-                    permission_index = -1
+                    permission_index = - 0
                     required_index = 99
 
                 has_permission = permission_index >= required_index
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 60, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1147,7 +1147,7 @@
                     required_index = permission_order.index(required_permission)
                 except ValueError:
                     permission_index = -1
-                    required_index = 99
+                    required_index = 100
 
                 has_permission = permission_index >= required_index
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.43s
operator: core/NumberReplacer, occurrence: 61, definition_name: user_has_permission
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1147,7 +1147,7 @@
                     required_index = permission_order.index(required_permission)
                 except ValueError:
                     permission_index = -1
-                    required_index = 99
+                    required_index = 98
 
                 has_permission = permission_index >= required_index
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 62, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1231,7 +1231,7 @@
 
                 for team_ref in allowed_teams:
                     # Extract team slug (handle both "org/team" and "team" formats)
-                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+                    team_slug = team_ref.split("/")[- 2] if "/" in team_ref else team_ref
 
                     try:
                         team = org.get_team_by_slug(team_slug)
........................................................................ [ 15%]
........................................................................ [ 30%]
.....................F
=================================== FAILURES ===================================
________ TestGetUserTeamSlugs.test_get_user_team_slugs_with_org_prefix _________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x7b593634fb10>

    def test_get_user_team_slugs_with_org_prefix(self):
        """Test team membership check with org/team format."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_user = Mock()
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(123456, "acme", "alice", ["acme/release-team"])
    
            # Should extract "release-team" from "acme/release-team"
>           mock_org.get_team_by_slug.assert_called_with("release-team")

tests/test_github_client.py:1775: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization().get_team_by_slug' id='135623062639280'>
args = ('release-team',), kwargs = {}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: get_team_by_slug('release-team')
E             Actual: get_team_by_slug('acme')

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_with_org_prefix - AssertionError: expected call not found.
Expected: get_team_by_slug('release-team')
  Actual: get_team_by_slug('acme')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 165 passed, 3 deselected, 1 warning in 1.48s
operator: core/NumberReplacer, occurrence: 63, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1231,7 +1231,7 @@
 
                 for team_ref in allowed_teams:
                     # Extract team slug (handle both "org/team" and "team" formats)
-                    team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
+                    team_slug = team_ref.split("/")[- 0] if "/" in team_ref else team_ref
 
                     try:
                         team = org.get_team_by_slug(team_slug)
........................................................................ [ 15%]
........................................................................ [ 30%]
.....................F
=================================== FAILURES ===================================
________ TestGetUserTeamSlugs.test_get_user_team_slugs_with_org_prefix _________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x787febc9fb10>

    def test_get_user_team_slugs_with_org_prefix(self):
        """Test team membership check with org/team format."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            mock_user = Mock()
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(123456, "acme", "alice", ["acme/release-team"])
    
            # Should extract "release-team" from "acme/release-team"
>           mock_org.get_team_by_slug.assert_called_with("release-team")

tests/test_github_client.py:1775: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Mock name='Github().get_organization().get_team_by_slug' id='132490781463216'>
args = ('release-team',), kwargs = {}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: get_team_by_slug('release-team')
E             Actual: get_team_by_slug('acme')

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_with_org_prefix - AssertionError: expected call not found.
Expected: get_team_by_slug('release-team')
  Actual: get_team_by_slug('acme')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 165 passed, 3 deselected, 1 warning in 1.50s
operator: core/RemoveDecorator, occurrence: 0, definition_name: GitHubAppClient
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -193,8 +193,6 @@
                 retry=_build_retry(),
             )
             self._initialized = True
-
-    @property
     def integration(self) -> GithubIntegration:
         """Get GitHub integration, initializing if needed.
 
........................................................................ [ 15%]
....................................F
=================================== FAILURES ===================================
_____ TestIntegrationProperty.test_integration_raises_when_not_initialized _____

self = <tests.test_github_client.TestIntegrationProperty object at 0x7a2528c89f90>

    def test_integration_raises_when_not_initialized(self):
        """Test that integration property raises when not initialized after _ensure_initialized."""
        with patch("stampbot.github_client.is_configured", return_value=False):
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
>           with pytest.raises(RuntimeError):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE RuntimeError

tests/test_github_client.py:130: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestIntegrationProperty::test_integration_raises_when_not_initialized - Failed: DID NOT RAISE RuntimeError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 108 passed, 3 deselected, 1 warning in 1.38s
operator: core/ZeroIterationForLoop, occurrence: 0, definition_name: _prune_rate_limit_metrics
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -322,7 +322,7 @@
         limit. Callers hold ``_installation_auths_lock``.
         """
         live = {str(installation_id) for installation_id in self._installation_auths}
-        for stale in self._rate_limit_installations - live:
+        for stale in []:
             # remove() is a no-op for a label set the gauge never held.
             github_api_rate_limit_remaining.remove(stale)
             github_api_rate_limit_limit.remove(stale)
........................................................................ [ 15%]
........................................................F
=================================== FAILURES ===================================
_ TestRateLimitLabelPruning.test_prune_removes_labels_for_evicted_installations _

self = <tests.test_github_client.TestRateLimitLabelPruning object at 0x75d6f32725d0>

    def test_prune_removes_labels_for_evicted_installations(self):
        """Test gauge label sets follow the credential cache, so churn cannot grow them."""
        from stampbot.github_client import GitHubAppClient
        from stampbot.metrics import github_api_rate_limit_limit, github_api_rate_limit_remaining
    
        client = GitHubAppClient()
        for installation_id in (9001, 9002):
            fake = Mock()
            fake.requester.rate_limiting = (10, 5000)
            client._update_rate_limit_metrics(fake, installation_id)
        assert client._rate_limit_installations == {"9001", "9002"}
    
        # Only 9002 still has live credentials.
        client._installation_auths[9002] = Mock()
        with client._installation_auths_lock:
            client._prune_rate_limit_metrics()
    
        assert client._rate_limit_installations == {"9002"}
        for gauge in (github_api_rate_limit_remaining, github_api_rate_limit_limit):
            labelled = {sample.labels["installation_id"] for sample in gauge.collect()[0].samples}
>           assert "9001" not in labelled
E           AssertionError: assert '9001' not in {'9001', '9002'}

tests/test_github_client.py:529: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestRateLimitLabelPruning::test_prune_removes_labels_for_evicted_installations - AssertionError: assert '9001' not in {'9001', '9002'}
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 128 passed, 3 deselected, 1 warning in 1.42s
operator: core/ZeroIterationForLoop, occurrence: 1, definition_name: find_bot_approval_reviews
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -811,7 +811,7 @@
                 bot_user = self._bot_login()
 
                 bot_reviews: list[BotReview] = []
-                for review in pr.get_reviews():
+                for review in []:
                     if review.user.login == bot_user and review.state in (
                         "APPROVED",
                         "DISMISSED",
........................................................................ [ 15%]
........................................................................ [ 30%]
..............F
=================================== FAILURES ===================================
______ TestFindBotApprovalReviews.test_find_bot_approval_reviews_success _______

self = <tests.test_github_client.TestFindBotApprovalReviews object at 0x74d1729334d0>

    def test_find_bot_approval_reviews_success(self):
        """Test successful bot approval review state lookup."""
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration.get_app.return_value = Mock(slug="stampbot")
            mock_integration_cls.return_value = mock_integration
    
            bot_approved_review = Mock()
            bot_approved_review.user.login = "stampbot[bot]"
            bot_approved_review.state = "APPROVED"
            bot_approved_review.id = 123
            bot_approved_review.commit_id = "headsha"
    
            bot_dismissed_review = Mock()
            bot_dismissed_review.user.login = "stampbot[bot]"
            bot_dismissed_review.state = "DISMISSED"
            bot_dismissed_review.id = 124
            bot_dismissed_review.commit_id = "oldsha"
    
            bot_commented_review = Mock()
            bot_commented_review.user.login = "stampbot[bot]"
            bot_commented_review.state = "COMMENTED"
            bot_commented_review.id = 125
    
            other_review = Mock()
            other_review.user.login = "other-user"
            other_review.state = "APPROVED"
            other_review.id = 456
    
            mock_pr = Mock()
            mock_pr.get_reviews.return_value = [
                bot_approved_review,
                bot_dismissed_review,
                bot_commented_review,
                other_review,
            ]
            mock_repo = Mock()
            mock_repo.get_pull.return_value = mock_pr
            mock_github = Mock()
            mock_github.get_repo.return_value = mock_repo
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.find_bot_approval_reviews(123456, "owner/repo", 42)
    
>           assert result == [
                {"id": 123, "state": "APPROVED", "commit_id": "headsha"},
                {"id": 124, "state": "DISMISSED", "commit_id": "oldsha"},
            ]
E           AssertionError: assert [] == [{'id': 123, ...d': 'oldsha'}]
E             
E             Right contains 2 more items, first extra item: {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E             + []
E             - [
E             -     {
E             -         'id': 123,
E             -         'state': 'APPROVED',
E             -         'commit_id': 'headsha',
E             -     },
E             -     {
E             -         'id': 124,
E             -         'state': 'DISMISSED',
E             -         'commit_id': 'oldsha',
E             -     },
E             - ]

tests/test_github_client.py:1481: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestFindBotApprovalReviews::test_find_bot_approval_reviews_success - AssertionError: assert [] == [{'id': 123, ...d': 'oldsha'}]
  
  Right contains 2 more items, first extra item: {'id': 123, 'state': 'APPROVED', 'commit_id': 'headsha'}
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     {
  -         'id': 123,
  -         'state': 'APPROVED',
  -         'commit_id': 'headsha',
  -     },
  -     {
  -         'id': 124,
  -         'state': 'DISMISSED',
  -         'commit_id': 'oldsha',
  -     },
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 158 passed, 3 deselected, 1 warning in 1.48s
operator: core/ZeroIterationForLoop, occurrence: 2, definition_name: get_user_team_slugs
--- mutation diff ---
--- a/stampbot/github_client.py
+++ b/stampbot/github_client.py
@@ -1229,7 +1229,7 @@
                 org = client.get_organization(org_name)
                 user = client.get_user(username)
 
-                for team_ref in allowed_teams:
+                for team_ref in []:
                     # Extract team slug (handle both "org/team" and "team" formats)
                     team_slug = team_ref.split("/")[-1] if "/" in team_ref else team_ref
 
........................................................................ [ 15%]
........................................................................ [ 30%]
....................F
=================================== FAILURES ===================================
____________ TestGetUserTeamSlugs.test_get_user_team_slugs_success _____________

self = <tests.test_github_client.TestGetUserTeamSlugs object at 0x71f3b14ff9d0>

    def test_get_user_team_slugs_success(self):
        """Test successful team membership check."""
    
        with (
            patch("stampbot.github_client.is_configured", return_value=True),
            patch("stampbot.github_client.settings") as mock_settings,
            patch("stampbot.github_client.Auth.AppAuth"),
            patch("stampbot.github_client.GithubIntegration") as mock_integration_cls,
            patch("stampbot.github_client.Github") as mock_github_cls,
            patch("stampbot.github_client.create_span") as mock_span,
        ):
            mock_settings.app_id = 12345
            mock_settings.private_key = TEST_PEM_KEY
            mock_settings.otel_enabled = False
    
            mock_integration = Mock()
            mock_token = Mock()
            mock_token.token = TEST_TOKEN
            mock_integration.get_access_token.return_value = mock_token
            mock_integration_cls.return_value = mock_integration
    
            # Mock user
            mock_user = Mock()
            mock_user.login = "alice"
    
            # Mock team that has alice as member
            mock_team = Mock()
            mock_team.has_in_members.return_value = True
    
            # Mock org
            mock_org = Mock()
            mock_org.get_team_by_slug.return_value = mock_team
    
            mock_github = Mock()
            mock_github.get_organization.return_value = mock_org
            mock_github.get_user.return_value = mock_user
            mock_github.get_rate_limit.return_value = Mock(core=Mock(remaining=4500, limit=5000))
            mock_github_cls.return_value = mock_github
    
            mock_span.return_value.__enter__ = Mock(return_value=None)
            mock_span.return_value.__exit__ = Mock(return_value=False)
    
            from stampbot.github_client import GitHubAppClient
    
            client = GitHubAppClient()
            result = client.get_user_team_slugs(
                123456, "acme", "alice", ["release-team", "deploy-team"]
            )
    
>           assert result == ["release-team", "deploy-team"]
E           AssertionError: assert [] == ['release-tea...'deploy-team']
E             
E             Right contains 2 more items, first extra item: 'release-team'
E             
E             Full diff: (-: missing in left side, +: extra in left side)
E             + []
E             - [
E             -     'release-team',
E             -     'deploy-team',
E             - ]

tests/test_github_client.py:1731: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_github_client.py::TestGetUserTeamSlugs::test_get_user_team_slugs_success - AssertionError: assert [] == ['release-tea...'deploy-team']
  
  Right contains 2 more items, first extra item: 'release-team'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     'release-team',
  -     'deploy-team',
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 164 passed, 3 deselected, 1 warning in 1.45s
operator: core/ReplaceBinaryOperator_Add_Sub, occurrence: 0, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) - len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................F
=================================== FAILURES ===================================
_______ test_webhook_streamed_body_over_limit_is_rejected_while_reading ________

    async def test_webhook_streamed_body_over_limit_is_rejected_while_reading():
        """An oversize streamed body is refused during the read, not after it.
    
        Without a ``Content-Length`` the pre-read check cannot see the size, so this
        is the case that used to reach ``Request.body()`` and buffer everything. The
        offered-bytes assertion is the regression guard: it fails if the limit moves
        back to a check that runs once the whole body is already in memory.
        """
        chunk_size = 64 * 1024
        total = 4 * MAX_WEBHOOK_BODY_SIZE
        offered = 0
    
        async def body():
            nonlocal offered
            for _ in range(total // chunk_size):
                offered += chunk_size
                yield b"x" * chunk_size
    
        with patch.object(webhook_handler, "verify_signature", return_value=True) as verify:
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
        assert response.status_code == 413
        assert response.json()["detail"] == "Request body too large"
        # Stopped within one chunk of the limit, nowhere near the 4 MiB on offer.
>       assert offered <= MAX_WEBHOOK_BODY_SIZE + chunk_size
E       assert 1245184 <= (1048576 + 65536)

tests/test_main.py:440: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:12:28.976597Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_over_limit_is_rejected_while_reading - assert 1245184 <= (1048576 + 65536)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 232 passed, 3 deselected, 1 warning in 2.12s
operator: core/ReplaceBinaryOperator_Add_Mul, occurrence: 0, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) * len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..................F
=================================== FAILURES ===================================
_______________ test_webhook_streamed_body_at_limit_is_processed _______________

    async def test_webhook_streamed_body_at_limit_is_processed():
        """A streamed body of exactly the limit is read and handled normally."""
        payload = _payload_of_exactly(MAX_WEBHOOK_BODY_SIZE)
        chunk_size = 64 * 1024
    
        async def body():
            for start in range(0, len(payload), chunk_size):
                yield payload[start : start + chunk_size]
    
        with patch.object(webhook_handler, "verify_signature", return_value=True):
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
>       assert response.status_code == 200
E       assert 413 == 200
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:497: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:21:09.331867Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_at_limit_is_processed - assert 413 == 200
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 234 passed, 3 deselected, 1 warning in 2.15s
operator: core/ReplaceBinaryOperator_Add_Div, occurrence: 0, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) / len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7d06bb58e9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:506: in webhook
    body = await _read_body_within_limit(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.requests.Request object at 0x7d06ba378c00>

    async def _read_body_within_limit(request: Request) -> bytes:
        """Read the request body, stopping as soon as it passes the size limit.
    
        ``Request.body()`` buffers the whole stream before anything can measure it.
        A chunked request carries no ``Content-Length``, so the pre-read check
        cannot see its size, and an unauthenticated client could make the process
        hold an arbitrarily large body in memory before the limit applied. Counting
        bytes as they arrive bounds that to ``MAX_WEBHOOK_BODY_SIZE`` plus the chunk
        that crosses it.
    
        The bytes accumulate in one contiguous buffer rather than a list of chunks.
        The client also controls chunk size, and a list would charge a separate
        object header and pointer per chunk: a body paced in two-byte chunks would
        cost about twenty times its length before the limit noticed anything.
    
        Args:
            request: The incoming request, whose stream has not been consumed.
    
        Returns:
            The complete body.
    
        Raises:
            HTTPException: 413 once the bytes read exceed ``MAX_WEBHOOK_BODY_SIZE``.
    
        Note:
            This consumes the stream without caching it on the request, so a later
            ``request.body()`` or ``request.json()`` raises ``RuntimeError``.
            Everything downstream must use the returned bytes.
        """
        body = bytearray()
        async for chunk in request.stream():
>           if len(body) / len(chunk) > MAX_WEBHOOK_BODY_SIZE:
               ^^^^^^^^^^^^^^^^^^^^^^
E           ZeroDivisionError: division by zero

stampbot/main.py:456: ZeroDivisionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:08:20.716849Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - ZeroDivisionError: division by zero
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.22s
operator: core/ReplaceBinaryOperator_Add_FloorDiv, occurrence: 0, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) // len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x78751df869c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:506: in webhook
    body = await _read_body_within_limit(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.requests.Request object at 0x78751cda8c00>

    async def _read_body_within_limit(request: Request) -> bytes:
        """Read the request body, stopping as soon as it passes the size limit.
    
        ``Request.body()`` buffers the whole stream before anything can measure it.
        A chunked request carries no ``Content-Length``, so the pre-read check
        cannot see its size, and an unauthenticated client could make the process
        hold an arbitrarily large body in memory before the limit applied. Counting
        bytes as they arrive bounds that to ``MAX_WEBHOOK_BODY_SIZE`` plus the chunk
        that crosses it.
    
        The bytes accumulate in one contiguous buffer rather than a list of chunks.
        The client also controls chunk size, and a list would charge a separate
        object header and pointer per chunk: a body paced in two-byte chunks would
        cost about twenty times its length before the limit noticed anything.
    
        Args:
            request: The incoming request, whose stream has not been consumed.
    
        Returns:
            The complete body.
    
        Raises:
            HTTPException: 413 once the bytes read exceed ``MAX_WEBHOOK_BODY_SIZE``.
    
        Note:
            This consumes the stream without caching it on the request, so a later
            ``request.body()`` or ``request.json()`` raises ``RuntimeError``.
            Everything downstream must use the returned bytes.
        """
        body = bytearray()
        async for chunk in request.stream():
>           if len(body) // len(chunk) > MAX_WEBHOOK_BODY_SIZE:
               ^^^^^^^^^^^^^^^^^^^^^^^
E           ZeroDivisionError: division by zero

stampbot/main.py:456: ZeroDivisionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:57:32.084991Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - ZeroDivisionError: division by zero
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.21s
operator: core/ReplaceBinaryOperator_Add_Mod, occurrence: 0, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) % len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x788993a6a9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:506: in webhook
    body = await _read_body_within_limit(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.requests.Request object at 0x788992878c00>

    async def _read_body_within_limit(request: Request) -> bytes:
        """Read the request body, stopping as soon as it passes the size limit.
    
        ``Request.body()`` buffers the whole stream before anything can measure it.
        A chunked request carries no ``Content-Length``, so the pre-read check
        cannot see its size, and an unauthenticated client could make the process
        hold an arbitrarily large body in memory before the limit applied. Counting
        bytes as they arrive bounds that to ``MAX_WEBHOOK_BODY_SIZE`` plus the chunk
        that crosses it.
    
        The bytes accumulate in one contiguous buffer rather than a list of chunks.
        The client also controls chunk size, and a list would charge a separate
        object header and pointer per chunk: a body paced in two-byte chunks would
        cost about twenty times its length before the limit noticed anything.
    
        Args:
            request: The incoming request, whose stream has not been consumed.
    
        Returns:
            The complete body.
    
        Raises:
            HTTPException: 413 once the bytes read exceed ``MAX_WEBHOOK_BODY_SIZE``.
    
        Note:
            This consumes the stream without caching it on the request, so a later
            ``request.body()`` or ``request.json()`` raises ``RuntimeError``.
            Everything downstream must use the returned bytes.
        """
        body = bytearray()
        async for chunk in request.stream():
>           if len(body) % len(chunk) > MAX_WEBHOOK_BODY_SIZE:
               ^^^^^^^^^^^^^^^^^^^^^^
E           ZeroDivisionError: division by zero

stampbot/main.py:456: ZeroDivisionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:10:59.628470Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - ZeroDivisionError: division by zero
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.21s
operator: core/ReplaceBinaryOperator_Add_Pow, occurrence: 0, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) ** len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.................F
=================================== FAILURES ===================================
_________ test_read_body_within_limit_holds_tiny_chunks_in_one_buffer __________

    async def test_read_body_within_limit_holds_tiny_chunks_in_one_buffer():
        """Many tiny chunks cost about the body's size, not a per-chunk overhead.
    
        The client chooses the chunk size. Kept as a list, every chunk carries its
        own object header and list slot, so two-byte chunks would multiply the
        memory held for a body by about twenty before the limit saw anything. The
        peak-allocation bound fails against that list-of-chunks implementation.
        """
        payload = _payload_of_exactly(64 * 1024)
        chunk_size = 2
    
        class TinyChunkRequest:
            async def stream(self):
                for start in range(0, len(payload), chunk_size):
                    yield payload[start : start + chunk_size]
    
        tracemalloc.start()
        try:
            tracemalloc.reset_peak()
>           body = await _read_body_within_limit(TinyChunkRequest())  # type: ignore[arg-type]
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_main.py:465: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <tests.test_main.test_read_body_within_limit_holds_tiny_chunks_in_one_buffer.<locals>.TinyChunkRequest object at 0x709b4f955010>

    async def _read_body_within_limit(request: Request) -> bytes:
        """Read the request body, stopping as soon as it passes the size limit.
    
        ``Request.body()`` buffers the whole stream before anything can measure it.
        A chunked request carries no ``Content-Length``, so the pre-read check
        cannot see its size, and an unauthenticated client could make the process
        hold an arbitrarily large body in memory before the limit applied. Counting
        bytes as they arrive bounds that to ``MAX_WEBHOOK_BODY_SIZE`` plus the chunk
        that crosses it.
    
        The bytes accumulate in one contiguous buffer rather than a list of chunks.
        The client also controls chunk size, and a list would charge a separate
        object header and pointer per chunk: a body paced in two-byte chunks would
        cost about twenty times its length before the limit noticed anything.
    
        Args:
            request: The incoming request, whose stream has not been consumed.
    
        Returns:
            The complete body.
    
        Raises:
            HTTPException: 413 once the bytes read exceed ``MAX_WEBHOOK_BODY_SIZE``.
    
        Note:
            This consumes the stream without caching it on the request, so a later
            ``request.body()`` or ``request.json()`` raises ``RuntimeError``.
            Everything downstream must use the returned bytes.
        """
        body = bytearray()
        async for chunk in request.stream():
            if len(body) ** len(chunk) > MAX_WEBHOOK_BODY_SIZE:
                errors_total.labels(error_type="payload_too_large").inc()
>               raise HTTPException(status_code=413, detail="Request body too large")
E               fastapi.exceptions.HTTPException: 413: Request body too large

stampbot/main.py:458: HTTPException
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_read_body_within_limit_holds_tiny_chunks_in_one_buffer - fastapi.exceptions.HTTPException: 413: Request body too large
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 233 passed, 3 deselected, 1 warning in 2.13s
operator: core/ReplaceBinaryOperator_Add_RShift, occurrence: 0, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) >> len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................F
=================================== FAILURES ===================================
_______ test_webhook_streamed_body_over_limit_is_rejected_while_reading ________

    async def test_webhook_streamed_body_over_limit_is_rejected_while_reading():
        """An oversize streamed body is refused during the read, not after it.
    
        Without a ``Content-Length`` the pre-read check cannot see the size, so this
        is the case that used to reach ``Request.body()`` and buffer everything. The
        offered-bytes assertion is the regression guard: it fails if the limit moves
        back to a check that runs once the whole body is already in memory.
        """
        chunk_size = 64 * 1024
        total = 4 * MAX_WEBHOOK_BODY_SIZE
        offered = 0
    
        async def body():
            nonlocal offered
            for _ in range(total // chunk_size):
                offered += chunk_size
                yield b"x" * chunk_size
    
        with patch.object(webhook_handler, "verify_signature", return_value=True) as verify:
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
        assert response.status_code == 413
        assert response.json()["detail"] == "Request body too large"
        # Stopped within one chunk of the limit, nowhere near the 4 MiB on offer.
>       assert offered <= MAX_WEBHOOK_BODY_SIZE + chunk_size
E       assert 4194304 <= (1048576 + 65536)

tests/test_main.py:440: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:15:04.958410Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_over_limit_is_rejected_while_reading - assert 4194304 <= (1048576 + 65536)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 232 passed, 3 deselected, 1 warning in 2.14s
operator: core/ReplaceBinaryOperator_Add_LShift, occurrence: 0, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) << len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..................F
=================================== FAILURES ===================================
_______________ test_webhook_streamed_body_at_limit_is_processed _______________

    async def test_webhook_streamed_body_at_limit_is_processed():
        """A streamed body of exactly the limit is read and handled normally."""
        payload = _payload_of_exactly(MAX_WEBHOOK_BODY_SIZE)
        chunk_size = 64 * 1024
    
        async def body():
            for start in range(0, len(payload), chunk_size):
                yield payload[start : start + chunk_size]
    
        with patch.object(webhook_handler, "verify_signature", return_value=True):
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
>       assert response.status_code == 200
E       assert 413 == 200
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:497: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:40:32.055098Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_at_limit_is_processed - assert 413 == 200
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 234 passed, 3 deselected, 1 warning in 2.18s
operator: core/ReplaceBinaryOperator_Add_BitOr, occurrence: 0, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) | len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.42s
operator: core/ReplaceBinaryOperator_Add_BitAnd, occurrence: 0, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) & len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................F
=================================== FAILURES ===================================
_______ test_webhook_streamed_body_over_limit_is_rejected_while_reading ________

    async def test_webhook_streamed_body_over_limit_is_rejected_while_reading():
        """An oversize streamed body is refused during the read, not after it.
    
        Without a ``Content-Length`` the pre-read check cannot see the size, so this
        is the case that used to reach ``Request.body()`` and buffer everything. The
        offered-bytes assertion is the regression guard: it fails if the limit moves
        back to a check that runs once the whole body is already in memory.
        """
        chunk_size = 64 * 1024
        total = 4 * MAX_WEBHOOK_BODY_SIZE
        offered = 0
    
        async def body():
            nonlocal offered
            for _ in range(total // chunk_size):
                offered += chunk_size
                yield b"x" * chunk_size
    
        with patch.object(webhook_handler, "verify_signature", return_value=True) as verify:
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
>       assert response.status_code == 413
E       assert 400 == 413
E        +  where 400 = <Response [400 Bad Request]>.status_code

tests/test_main.py:437: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)", "client_ip": "127.0.0.1", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:06:35.881268Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 400 Bad Request\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:06:35.881677Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:523 {'event': 'Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)', 'client_ip': '127.0.0.1', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:06:35.881268Z'}
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_over_limit_is_rejected_while_reading - assert 400 == 413
 +  where 400 = <Response [400 Bad Request]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 232 passed, 3 deselected, 1 warning in 2.11s
operator: core/ReplaceBinaryOperator_Add_BitXor, occurrence: 0, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) ^ len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 0, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -250,7 +250,7 @@
     try:
         response = await call_next(request)
 
-        duration = time.time() - start_time
+        duration = time.time() + start_time
 
         # Track request metrics
         http_requests_total.labels(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.31s
operator: core/ReplaceBinaryOperator_Sub_Add, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -535,7 +535,7 @@
         result = await webhook_handler.handle_event(
             x_github_event, payload, delivery_id=delivery_id
         )
-        duration = time.time() - start_time
+        duration = time.time() + start_time
 
         webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
             duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.41s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 0, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -250,7 +250,7 @@
     try:
         response = await call_next(request)
 
-        duration = time.time() - start_time
+        duration = time.time() * start_time
 
         # Track request metrics
         http_requests_total.labels(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Mul, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -535,7 +535,7 @@
         result = await webhook_handler.handle_event(
             x_github_event, payload, delivery_id=delivery_id
         )
-        duration = time.time() - start_time
+        duration = time.time() * start_time
 
         webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
             duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 0, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -250,7 +250,7 @@
     try:
         response = await call_next(request)
 
-        duration = time.time() - start_time
+        duration = time.time() / start_time
 
         # Track request metrics
         http_requests_total.labels(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceBinaryOperator_Sub_Div, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -535,7 +535,7 @@
         result = await webhook_handler.handle_event(
             x_github_event, payload, delivery_id=delivery_id
         )
-        duration = time.time() - start_time
+        duration = time.time() / start_time
 
         webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
             duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 0, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -250,7 +250,7 @@
     try:
         response = await call_next(request)
 
-        duration = time.time() - start_time
+        duration = time.time() // start_time
 
         # Track request metrics
         http_requests_total.labels(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_FloorDiv, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -535,7 +535,7 @@
         result = await webhook_handler.handle_event(
             x_github_event, payload, delivery_id=delivery_id
         )
-        duration = time.time() - start_time
+        duration = time.time() // start_time
 
         webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
             duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 0, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -250,7 +250,7 @@
     try:
         response = await call_next(request)
 
-        duration = time.time() - start_time
+        duration = time.time() % start_time
 
         # Track request metrics
         http_requests_total.labels(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceBinaryOperator_Sub_Mod, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -535,7 +535,7 @@
         result = await webhook_handler.handle_event(
             x_github_event, payload, delivery_id=delivery_id
         )
-        duration = time.time() - start_time
+        duration = time.time() % start_time
 
         webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
             duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 0, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -250,7 +250,7 @@
     try:
         response = await call_next(request)
 
-        duration = time.time() - start_time
+        duration = time.time() ** start_time
 
         # Track request metrics
         http_requests_total.labels(
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x7de524425fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='138423109314816'>, 'http_request_duration_seconds': ...='138423109315488'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='138423109315824'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x7de524468980>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x7de524461380>

    @app.middleware("http")
    async def metrics_middleware(request: Request, call_next: Any) -> Response:
        """Middleware to track HTTP metrics.
    
        Args:
            request: Incoming HTTP request.
            call_next: Next middleware or route handler.
    
        Returns:
            HTTP response from downstream handler.
        """
        method = request.method
        endpoint = _metric_endpoint_label(request)
    
        # Track in-progress requests
        http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
    
        start_time = time.time()
    
        try:
            response = await call_next(request)
    
>           duration = time.time() ** start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^^
E           OverflowError: (34, 'Numerical result out of range')

stampbot/main.py:253: OverflowError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:47:31.057019Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - OverflowError: (34, 'Numerical result out of range')
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.60s
operator: core/ReplaceBinaryOperator_Sub_Pow, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -535,7 +535,7 @@
         result = await webhook_handler.handle_event(
             x_github_event, payload, delivery_id=delivery_id
         )
-        duration = time.time() - start_time
+        duration = time.time() ** start_time
 
         webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
             duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x7e5c5a08b240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
>       assert response.status_code == 200
E       assert 500 == 200
E        +  where 500 = <Response [500 Internal Server Error]>.status_code

tests/test_main.py:365: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:42:01.505559Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"error": "(34, 'Numerical result out of range')"}, "event": "Error handling webhook event: (34, 'Numerical result out of range')", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:42:01.505710Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:42:01.506149Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:42:01.505559Z'}
ERROR    stampbot.main:main.py:548 {'extra': {'error': "(34, 'Numerical result out of range')"}, 'event': "Error handling webhook event: (34, 'Numerical result out of range')", 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:42:01.505710Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
 +  where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.11s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 0, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -250,7 +250,7 @@
     try:
         response = await call_next(request)
 
-        duration = time.time() - start_time
+        duration = time.time() >> start_time
 
         # Track request metrics
         http_requests_total.labels(
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x744b9edf5fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='127868136810752'>, 'http_request_duration_seconds': ...='127868136811424'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='127868136811760'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x744b9ec38980>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x744b9ec31380>

    @app.middleware("http")
    async def metrics_middleware(request: Request, call_next: Any) -> Response:
        """Middleware to track HTTP metrics.
    
        Args:
            request: Incoming HTTP request.
            call_next: Next middleware or route handler.
    
        Returns:
            HTTP response from downstream handler.
        """
        method = request.method
        endpoint = _metric_endpoint_label(request)
    
        # Track in-progress requests
        http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
    
        start_time = time.time()
    
        try:
            response = await call_next(request)
    
>           duration = time.time() >> start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for >>: 'float' and 'float'

stampbot/main.py:253: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:13:33.960349Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: unsupported operand type(s) for >>: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.53s
operator: core/ReplaceBinaryOperator_Sub_RShift, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -535,7 +535,7 @@
         result = await webhook_handler.handle_event(
             x_github_event, payload, delivery_id=delivery_id
         )
-        duration = time.time() - start_time
+        duration = time.time() >> start_time
 
         webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
             duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x78ac49d7b240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
>       assert response.status_code == 200
E       assert 500 == 200
E        +  where 500 = <Response [500 Internal Server Error]>.status_code

tests/test_main.py:365: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:11:20.202035Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"error": "unsupported operand type(s) for >>: 'float' and 'float'"}, "event": "Error handling webhook event: unsupported operand type(s) for >>: 'float' and 'float'", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:11:20.202190Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:11:20.202631Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:11:20.202035Z'}
ERROR    stampbot.main:main.py:548 {'extra': {'error': "unsupported operand type(s) for >>: 'float' and 'float'"}, 'event': "Error handling webhook event: unsupported operand type(s) for >>: 'float' and 'float'", 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:11:20.202190Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
 +  where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.11s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 0, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -250,7 +250,7 @@
     try:
         response = await call_next(request)
 
-        duration = time.time() - start_time
+        duration = time.time() << start_time
 
         # Track request metrics
         http_requests_total.labels(
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x74ccea9f9fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='128423458482432'>, 'http_request_duration_seconds': ...='128423458483104'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='128423458483440'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x74ccea83c980>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x74ccea835380>

    @app.middleware("http")
    async def metrics_middleware(request: Request, call_next: Any) -> Response:
        """Middleware to track HTTP metrics.
    
        Args:
            request: Incoming HTTP request.
            call_next: Next middleware or route handler.
    
        Returns:
            HTTP response from downstream handler.
        """
        method = request.method
        endpoint = _metric_endpoint_label(request)
    
        # Track in-progress requests
        http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
    
        start_time = time.time()
    
        try:
            response = await call_next(request)
    
>           duration = time.time() << start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for <<: 'float' and 'float'

stampbot/main.py:253: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:51:38.893339Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: unsupported operand type(s) for <<: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.57s
operator: core/ReplaceBinaryOperator_Sub_LShift, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -535,7 +535,7 @@
         result = await webhook_handler.handle_event(
             x_github_event, payload, delivery_id=delivery_id
         )
-        duration = time.time() - start_time
+        duration = time.time() << start_time
 
         webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
             duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x72d37da4f240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
>       assert response.status_code == 200
E       assert 500 == 200
E        +  where 500 = <Response [500 Internal Server Error]>.status_code

tests/test_main.py:365: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:20:32.162934Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"error": "unsupported operand type(s) for <<: 'float' and 'float'"}, "event": "Error handling webhook event: unsupported operand type(s) for <<: 'float' and 'float'", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:20:32.163071Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:20:32.163528Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:20:32.162934Z'}
ERROR    stampbot.main:main.py:548 {'extra': {'error': "unsupported operand type(s) for <<: 'float' and 'float'"}, 'event': "Error handling webhook event: unsupported operand type(s) for <<: 'float' and 'float'", 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:20:32.163071Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
 +  where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.12s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 0, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -250,7 +250,7 @@
     try:
         response = await call_next(request)
 
-        duration = time.time() - start_time
+        duration = time.time() | start_time
 
         # Track request metrics
         http_requests_total.labels(
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x75414c2f1fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='128923311483136'>, 'http_request_duration_seconds': ...='128923311483808'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='128923311484144'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x75414c134980>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x75414c12d380>

    @app.middleware("http")
    async def metrics_middleware(request: Request, call_next: Any) -> Response:
        """Middleware to track HTTP metrics.
    
        Args:
            request: Incoming HTTP request.
            call_next: Next middleware or route handler.
    
        Returns:
            HTTP response from downstream handler.
        """
        method = request.method
        endpoint = _metric_endpoint_label(request)
    
        # Track in-progress requests
        http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
    
        start_time = time.time()
    
        try:
            response = await call_next(request)
    
>           duration = time.time() | start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for |: 'float' and 'float'

stampbot/main.py:253: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:35:33.834326Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: unsupported operand type(s) for |: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.59s
operator: core/ReplaceBinaryOperator_Sub_BitOr, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -535,7 +535,7 @@
         result = await webhook_handler.handle_event(
             x_github_event, payload, delivery_id=delivery_id
         )
-        duration = time.time() - start_time
+        duration = time.time() | start_time
 
         webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
             duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x713deca7f240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
>       assert response.status_code == 200
E       assert 500 == 200
E        +  where 500 = <Response [500 Internal Server Error]>.status_code

tests/test_main.py:365: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:10:43.470716Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"error": "unsupported operand type(s) for |: 'float' and 'float'"}, "event": "Error handling webhook event: unsupported operand type(s) for |: 'float' and 'float'", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:10:43.470854Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:10:43.471204Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:10:43.470716Z'}
ERROR    stampbot.main:main.py:548 {'extra': {'error': "unsupported operand type(s) for |: 'float' and 'float'"}, 'event': "Error handling webhook event: unsupported operand type(s) for |: 'float' and 'float'", 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:10:43.470854Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
 +  where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.12s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 0, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -250,7 +250,7 @@
     try:
         response = await call_next(request)
 
-        duration = time.time() - start_time
+        duration = time.time() & start_time
 
         # Track request metrics
         http_requests_total.labels(
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x759df30f5fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='129321248188672'>, 'http_request_duration_seconds': ...='129321248189344'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='129321248189680'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x759df2f38980>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x759df2f31380>

    @app.middleware("http")
    async def metrics_middleware(request: Request, call_next: Any) -> Response:
        """Middleware to track HTTP metrics.
    
        Args:
            request: Incoming HTTP request.
            call_next: Next middleware or route handler.
    
        Returns:
            HTTP response from downstream handler.
        """
        method = request.method
        endpoint = _metric_endpoint_label(request)
    
        # Track in-progress requests
        http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
    
        start_time = time.time()
    
        try:
            response = await call_next(request)
    
>           duration = time.time() & start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for &: 'float' and 'float'

stampbot/main.py:253: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:47:44.518983Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: unsupported operand type(s) for &: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.59s
operator: core/ReplaceBinaryOperator_Sub_BitAnd, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -535,7 +535,7 @@
         result = await webhook_handler.handle_event(
             x_github_event, payload, delivery_id=delivery_id
         )
-        duration = time.time() - start_time
+        duration = time.time() & start_time
 
         webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
             duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x77d812e6b240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
>       assert response.status_code == 200
E       assert 500 == 200
E        +  where 500 = <Response [500 Internal Server Error]>.status_code

tests/test_main.py:365: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:41:06.200748Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"error": "unsupported operand type(s) for &: 'float' and 'float'"}, "event": "Error handling webhook event: unsupported operand type(s) for &: 'float' and 'float'", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:41:06.200891Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:41:06.201386Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:41:06.200748Z'}
ERROR    stampbot.main:main.py:548 {'extra': {'error': "unsupported operand type(s) for &: 'float' and 'float'"}, 'event': "Error handling webhook event: unsupported operand type(s) for &: 'float' and 'float'", 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:41:06.200891Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
 +  where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.15s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 0, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -250,7 +250,7 @@
     try:
         response = await call_next(request)
 
-        duration = time.time() - start_time
+        duration = time.time() ^ start_time
 
         # Track request metrics
         http_requests_total.labels(
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x76cf8d705fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='130633803262208'>, 'http_request_duration_seconds': ...='130633803262880'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='130633803263216'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x76cf8d748980>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x76cf8d741380>

    @app.middleware("http")
    async def metrics_middleware(request: Request, call_next: Any) -> Response:
        """Middleware to track HTTP metrics.
    
        Args:
            request: Incoming HTTP request.
            call_next: Next middleware or route handler.
    
        Returns:
            HTTP response from downstream handler.
        """
        method = request.method
        endpoint = _metric_endpoint_label(request)
    
        # Track in-progress requests
        http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
    
        start_time = time.time()
    
        try:
            response = await call_next(request)
    
>           duration = time.time() ^ start_time
                       ^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: unsupported operand type(s) for ^: 'float' and 'float'

stampbot/main.py:253: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:36:17.731106Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: unsupported operand type(s) for ^: 'float' and 'float'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceBinaryOperator_Sub_BitXor, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -535,7 +535,7 @@
         result = await webhook_handler.handle_event(
             x_github_event, payload, delivery_id=delivery_id
         )
-        duration = time.time() - start_time
+        duration = time.time() ^ start_time
 
         webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
             duration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x7b98e8d6b240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
>       assert response.status_code == 200
E       assert 500 == 200
E        +  where 500 = <Response [500 Internal Server Error]>.status_code

tests/test_main.py:365: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:31:28.865151Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"extra": {"error": "unsupported operand type(s) for ^: 'float' and 'float'"}, "event": "Error handling webhook event: unsupported operand type(s) for ^: 'float' and 'float'", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T03:31:28.865315Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:31:28.865759Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:31:28.865151Z'}
ERROR    stampbot.main:main.py:548 {'extra': {'error': "unsupported operand type(s) for ^: 'float' and 'float'"}, 'event': "Error handling webhook event: unsupported operand type(s) for ^: 'float' and 'float'", 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T03:31:28.865315Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - assert 500 == 200
 +  where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.11s
operator: core/ReplaceBinaryOperator_Mul_Add, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 + 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................F
=================================== FAILURES ===================================
_______ test_webhook_streamed_body_over_limit_is_rejected_while_reading ________

    async def test_webhook_streamed_body_over_limit_is_rejected_while_reading():
        """An oversize streamed body is refused during the read, not after it.
    
        Without a ``Content-Length`` the pre-read check cannot see the size, so this
        is the case that used to reach ``Request.body()`` and buffer everything. The
        offered-bytes assertion is the regression guard: it fails if the limit moves
        back to a check that runs once the whole body is already in memory.
        """
        chunk_size = 64 * 1024
        total = 4 * MAX_WEBHOOK_BODY_SIZE
        offered = 0
    
        async def body():
            nonlocal offered
            for _ in range(total // chunk_size):
                offered += chunk_size
                yield b"x" * chunk_size
    
        with patch.object(webhook_handler, "verify_signature", return_value=True) as verify:
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
>       assert response.status_code == 413
E       assert 400 == 413
E        +  where 400 = <Response [400 Bad Request]>.status_code

tests/test_main.py:437: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)", "client_ip": "127.0.0.1", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:27:39.541358Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 400 Bad Request\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:27:39.541730Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:523 {'event': 'Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)', 'client_ip': '127.0.0.1', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:27:39.541358Z'}
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_over_limit_is_rejected_while_reading - assert 400 == 413
 +  where 400 = <Response [400 Bad Request]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 232 passed, 3 deselected, 1 warning in 2.13s
operator: core/ReplaceBinaryOperator_Mul_Sub, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 - 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7ff5f97629c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:35:45.896247Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.13s
operator: core/ReplaceBinaryOperator_Mul_Div, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 / 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7b2ac7f769c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:04:16.179804Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.10s
operator: core/ReplaceBinaryOperator_Mul_FloorDiv, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 // 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7b7cceea29c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:40:10.896128Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.11s
operator: core/ReplaceBinaryOperator_Mul_Mod, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 % 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7546f78929c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:16:28.186163Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.11s
operator: core/ReplaceBinaryOperator_Mul_Pow, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 ** 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________

test_client = <starlette.testclient.TestClient object at 0x7f095799b9b0>

    def test_webhook_content_length_too_large(test_client: TestClient):
        """A declared-oversize body is refused without reading it."""
>       response = test_client.post(
            "/webhook",
            content=b"x",  # Small actual body
            headers={
                "Content-Type": "application/json",
                "Content-Length": str(MAX_WEBHOOK_BODY_SIZE + 1),
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=fake",
            },
        )

tests/test_main.py:373: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:273: in metrics_middleware
    ).observe(int(content_length))
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:678: in observe
    self._sum.inc(amount)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <prometheus_client.values.MutexValue object at 0x7f0957b920f0>
amount = 352497141210838265...1281816131942219777

    def inc(self, amount):
        with self._lock:
>           self._value += amount
E           OverflowError: int too large to convert to float

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/values.py:29: OverflowError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:30:21.043507Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - OverflowError: int too large to convert to float
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 231 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceBinaryOperator_Mul_RShift, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 >> 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7a3d3827a9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:20:48.361920Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.12s
operator: core/ReplaceBinaryOperator_Mul_LShift, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 << 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________

test_client = <starlette.testclient.TestClient object at 0x70c8b70739b0>

    def test_webhook_content_length_too_large(test_client: TestClient):
        """A declared-oversize body is refused without reading it."""
>       response = test_client.post(
            "/webhook",
            content=b"x",  # Small actual body
            headers={
                "Content-Type": "application/json",
                "Content-Length": str(MAX_WEBHOOK_BODY_SIZE + 1),
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=fake",
            },
        )

tests/test_main.py:373: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:273: in metrics_middleware
    ).observe(int(content_length))
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/metrics.py:678: in observe
    self._sum.inc(amount)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <prometheus_client.values.MutexValue object at 0x70c8b7268590>
amount = 184083777009901148...4881535205516509185

    def inc(self, amount):
        with self._lock:
>           self._value += amount
E           OverflowError: int too large to convert to float

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/values.py:29: OverflowError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:50:23.292514Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - OverflowError: int too large to convert to float
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 231 passed, 3 deselected, 1 warning in 2.22s
operator: core/ReplaceBinaryOperator_Mul_BitOr, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 | 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................F
=================================== FAILURES ===================================
_______ test_webhook_streamed_body_over_limit_is_rejected_while_reading ________

    async def test_webhook_streamed_body_over_limit_is_rejected_while_reading():
        """An oversize streamed body is refused during the read, not after it.
    
        Without a ``Content-Length`` the pre-read check cannot see the size, so this
        is the case that used to reach ``Request.body()`` and buffer everything. The
        offered-bytes assertion is the regression guard: it fails if the limit moves
        back to a check that runs once the whole body is already in memory.
        """
        chunk_size = 64 * 1024
        total = 4 * MAX_WEBHOOK_BODY_SIZE
        offered = 0
    
        async def body():
            nonlocal offered
            for _ in range(total // chunk_size):
                offered += chunk_size
                yield b"x" * chunk_size
    
        with patch.object(webhook_handler, "verify_signature", return_value=True) as verify:
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
>       assert response.status_code == 413
E       assert 400 == 413
E        +  where 400 = <Response [400 Bad Request]>.status_code

tests/test_main.py:437: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)", "client_ip": "127.0.0.1", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T03:38:16.909589Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 400 Bad Request\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:38:16.909955Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:523 {'event': 'Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)', 'client_ip': '127.0.0.1', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T03:38:16.909589Z'}
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_over_limit_is_rejected_while_reading - assert 400 == 413
 +  where 400 = <Response [400 Bad Request]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 232 passed, 3 deselected, 1 warning in 2.12s
operator: core/ReplaceBinaryOperator_Mul_BitAnd, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 & 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................F
=================================== FAILURES ===================================
_______ test_webhook_streamed_body_over_limit_is_rejected_while_reading ________

    async def test_webhook_streamed_body_over_limit_is_rejected_while_reading():
        """An oversize streamed body is refused during the read, not after it.
    
        Without a ``Content-Length`` the pre-read check cannot see the size, so this
        is the case that used to reach ``Request.body()`` and buffer everything. The
        offered-bytes assertion is the regression guard: it fails if the limit moves
        back to a check that runs once the whole body is already in memory.
        """
        chunk_size = 64 * 1024
        total = 4 * MAX_WEBHOOK_BODY_SIZE
        offered = 0
    
        async def body():
            nonlocal offered
            for _ in range(total // chunk_size):
                offered += chunk_size
                yield b"x" * chunk_size
    
        with patch.object(webhook_handler, "verify_signature", return_value=True) as verify:
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
>       assert response.status_code == 413
E       assert 400 == 413
E        +  where 400 = <Response [400 Bad Request]>.status_code

tests/test_main.py:437: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)", "client_ip": "127.0.0.1", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:25:00.474554Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 400 Bad Request\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:25:00.474920Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:523 {'event': 'Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)', 'client_ip': '127.0.0.1', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:25:00.474554Z'}
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_over_limit_is_rejected_while_reading - assert 400 == 413
 +  where 400 = <Response [400 Bad Request]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 232 passed, 3 deselected, 1 warning in 2.11s
operator: core/ReplaceBinaryOperator_Mul_BitXor, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 ^ 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x793417faa9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:47:41.831457Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.16s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -161,7 +161,7 @@
             raise RuntimeError("metrics_host must not be empty")
         if not 1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
-        if metrics_port == int(settings.port):
+        if metrics_port != int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
         metrics_runtime = start_metrics_server(metrics_host, metrics_port)
         logger.info(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x7c84af86cad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 <= metrics_port <= 65535:
                raise RuntimeError("metrics_port must be between 1 and 65535")
            if metrics_port != int(settings.port):
>               raise RuntimeError("metrics_port must differ from the public HTTP port")
E               RuntimeError: metrics_port must differ from the public HTTP port

stampbot/main.py:165: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:28:20.894517Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:28:20.894781Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:28:20.894517Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:28:20.894781Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must differ from the public HTTP port
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.18s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 1, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -314,7 +314,7 @@
             receive: ASGI receive callable.
             send: ASGI send callable.
         """
-        if scope["type"] == "http":
+        if scope["type"] != "http":
             structlog.contextvars.clear_contextvars()
 
             client_ip: str | None = None
........................................................................ [ 15%]
........................................................................ [ 30%]
.................................................................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________

    def test_lifespan_startup_shutdown_configured():
        """Test app lifespan startup and shutdown when configured."""
        from stampbot.main import app
    
        # Use TestClient as context manager to trigger lifespan events
>       with TestClient(app) as client:
             ^^^^^^^^^^^^^^^

tests/test_main.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:602: in wait_startup
    message = await receive()
              ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:324: in __call__
    raw = Request(scope).headers.get(client_ip_header)
          ^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:219: in __init__
    super().__init__(scope)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x70a65e3b3dd0>
scope = {'type': 'lifespan', 'state': {}, 'app': <fastapi.applications.FastAPI object at 0x70a65e768ad0>}
receive = None

    def __init__(self, scope: Scope, receive: Receive | None = None) -> None:
>       assert scope["type"] in ("http", "websocket")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AssertionError

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:88: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 209 passed, 3 deselected, 1 warning in 1.64s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -161,7 +161,7 @@
             raise RuntimeError("metrics_host must not be empty")
         if not 1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
-        if metrics_port == int(settings.port):
+        if metrics_port < int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
         metrics_runtime = start_metrics_server(metrics_host, metrics_port)
         logger.info(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.....F
=================================== FAILURES ===================================
_________________ test_lifespan_rejects_metrics_on_public_port _________________

    def test_lifespan_rejects_metrics_on_public_port():
        """Test that the metrics listener cannot reuse the public HTTP port."""
        from stampbot.main import app
    
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 8000
    
>           with pytest.raises(RuntimeError, match="must differ"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE RuntimeError

tests/test_main.py:218: Failed
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:19:10.477519Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:19:10.477643Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"extra": {"host": "127.0.0.1", "port": 8000}, "event": "Prometheus metrics listener started", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:19:10.478015Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "Shutting down stampbot", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:19:10.978884Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:19:10.477519Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:19:10.477643Z'}
INFO     stampbot.main:main.py:167 {'extra': {'host': '127.0.0.1', 'port': 8000}, 'event': 'Prometheus metrics listener started', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:19:10.478015Z'}
INFO     stampbot.main:main.py:178 {'event': 'Shutting down stampbot', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:19:10.978884Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_rejects_metrics_on_public_port - Failed: DID NOT RAISE RuntimeError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 221 passed, 3 deselected, 1 warning in 2.62s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 1, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -314,7 +314,7 @@
             receive: ASGI receive callable.
             send: ASGI send callable.
         """
-        if scope["type"] == "http":
+        if scope["type"] < "http":
             structlog.contextvars.clear_contextvars()
 
             client_ip: str | None = None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________

    def test_logging_middleware_respects_configured_header():
        """Test that logging_middleware uses the configured client_ip_header setting."""
        from unittest.mock import patch
    
        import structlog.contextvars
    
        from stampbot.main import app
    
        with patch("stampbot.main.settings") as mock_settings:
            mock_settings.get = lambda key, default=None: (
                "X-Real-IP" if key == "client_ip_header" else default
            )
    
            client = TestClient(app)
            captured: dict[str, str | None] = {}
    
            original_bind = structlog.contextvars.bind_contextvars
    
            def capturing_bind(**kw: object) -> None:
                captured.update({k: str(v) for k, v in kw.items()})
                original_bind(**kw)
    
            with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
                client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
    
>           assert captured.get("client_ip") == "198.51.100.7"
E           AssertionError: assert None == '198.51.100.7'
E            +  where None = <built-in method get of dict object at 0x745bc01a4900>('client_ip')
E            +    where <built-in method get of dict object at 0x745bc01a4900> = {}.get

tests/test_main.py:594: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/health \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:57:05.780021Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert None == '198.51.100.7'
 +  where None = <built-in method get of dict object at 0x745bc01a4900>('client_ip')
 +    where <built-in method get of dict object at 0x745bc01a4900> = {}.get
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 239 passed, 3 deselected, 1 warning in 2.19s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -161,7 +161,7 @@
             raise RuntimeError("metrics_host must not be empty")
         if not 1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
-        if metrics_port == int(settings.port):
+        if metrics_port <= int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
         metrics_runtime = start_metrics_server(metrics_host, metrics_port)
         logger.info(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 1, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -314,7 +314,7 @@
             receive: ASGI receive callable.
             send: ASGI send callable.
         """
-        if scope["type"] == "http":
+        if scope["type"] <= "http":
             structlog.contextvars.clear_contextvars()
 
             client_ip: str | None = None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -161,7 +161,7 @@
             raise RuntimeError("metrics_host must not be empty")
         if not 1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
-        if metrics_port == int(settings.port):
+        if metrics_port > int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
         metrics_runtime = start_metrics_server(metrics_host, metrics_port)
         logger.info(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x7a70e867cad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 <= metrics_port <= 65535:
                raise RuntimeError("metrics_port must be between 1 and 65535")
            if metrics_port > int(settings.port):
>               raise RuntimeError("metrics_port must differ from the public HTTP port")
E               RuntimeError: metrics_port must differ from the public HTTP port

stampbot/main.py:165: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:45:23.961753Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:45:23.962013Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:45:23.961753Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:45:23.962013Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must differ from the public HTTP port
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.24s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 1, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -314,7 +314,7 @@
             receive: ASGI receive callable.
             send: ASGI send callable.
         """
-        if scope["type"] == "http":
+        if scope["type"] > "http":
             structlog.contextvars.clear_contextvars()
 
             client_ip: str | None = None
........................................................................ [ 15%]
........................................................................ [ 30%]
.................................................................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________

    def test_lifespan_startup_shutdown_configured():
        """Test app lifespan startup and shutdown when configured."""
        from stampbot.main import app
    
        # Use TestClient as context manager to trigger lifespan events
>       with TestClient(app) as client:
             ^^^^^^^^^^^^^^^

tests/test_main.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:602: in wait_startup
    message = await receive()
              ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:324: in __call__
    raw = Request(scope).headers.get(client_ip_header)
          ^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:219: in __init__
    super().__init__(scope)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x721945c6bdd0>
scope = {'type': 'lifespan', 'state': {}, 'app': <fastapi.applications.FastAPI object at 0x721945f88ad0>}
receive = None

    def __init__(self, scope: Scope, receive: Receive | None = None) -> None:
>       assert scope["type"] in ("http", "websocket")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AssertionError

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:88: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 209 passed, 3 deselected, 1 warning in 1.58s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -161,7 +161,7 @@
             raise RuntimeError("metrics_host must not be empty")
         if not 1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
-        if metrics_port == int(settings.port):
+        if metrics_port >= int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
         metrics_runtime = start_metrics_server(metrics_host, metrics_port)
         logger.info(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x7c4bbbd90ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 <= metrics_port <= 65535:
                raise RuntimeError("metrics_port must be between 1 and 65535")
            if metrics_port >= int(settings.port):
>               raise RuntimeError("metrics_port must differ from the public HTTP port")
E               RuntimeError: metrics_port must differ from the public HTTP port

stampbot/main.py:165: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:27:20.242904Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:27:20.243158Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:27:20.242904Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:27:20.243158Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must differ from the public HTTP port
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.18s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 1, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -314,7 +314,7 @@
             receive: ASGI receive callable.
             send: ASGI send callable.
         """
-        if scope["type"] == "http":
+        if scope["type"] >= "http":
             structlog.contextvars.clear_contextvars()
 
             client_ip: str | None = None
........................................................................ [ 15%]
........................................................................ [ 30%]
.................................................................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________

    def test_lifespan_startup_shutdown_configured():
        """Test app lifespan startup and shutdown when configured."""
        from stampbot.main import app
    
        # Use TestClient as context manager to trigger lifespan events
>       with TestClient(app) as client:
             ^^^^^^^^^^^^^^^

tests/test_main.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:602: in wait_startup
    message = await receive()
              ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:324: in __call__
    raw = Request(scope).headers.get(client_ip_header)
          ^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:219: in __init__
    super().__init__(scope)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x71a200138c00>
scope = {'type': 'lifespan', 'state': {}, 'app': <fastapi.applications.FastAPI object at 0x71a20047cad0>}
receive = None

    def __init__(self, scope: Scope, receive: Receive | None = None) -> None:
>       assert scope["type"] in ("http", "websocket")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AssertionError

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:88: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 209 passed, 3 deselected, 1 warning in 1.59s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -161,7 +161,7 @@
             raise RuntimeError("metrics_host must not be empty")
         if not 1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
-        if metrics_port == int(settings.port):
+        if metrics_port is int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
         metrics_runtime = start_metrics_server(metrics_host, metrics_port)
         logger.info(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 1, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -314,7 +314,7 @@
             receive: ASGI receive callable.
             send: ASGI send callable.
         """
-        if scope["type"] == "http":
+        if scope["type"] is "http":
             structlog.contextvars.clear_contextvars()
 
             client_ip: str | None = None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/main.py:317
  /home/runner/work/stampbot/stampbot/stampbot/main.py:317: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if scope["type"] is "http":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 2 warnings in 3.35s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -161,7 +161,7 @@
             raise RuntimeError("metrics_host must not be empty")
         if not 1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
-        if metrics_port == int(settings.port):
+        if metrics_port is not int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
         metrics_runtime = start_metrics_server(metrics_host, metrics_port)
         logger.info(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x765db0e98ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 <= metrics_port <= 65535:
                raise RuntimeError("metrics_port must be between 1 and 65535")
            if metrics_port is not int(settings.port):
>               raise RuntimeError("metrics_port must differ from the public HTTP port")
E               RuntimeError: metrics_port must differ from the public HTTP port

stampbot/main.py:165: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:17:47.553505Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:17:47.553723Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:17:47.553505Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:17:47.553723Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must differ from the public HTTP port
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.20s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 1, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -314,7 +314,7 @@
             receive: ASGI receive callable.
             send: ASGI send callable.
         """
-        if scope["type"] == "http":
+        if scope["type"] is not "http":
             structlog.contextvars.clear_contextvars()
 
             client_ip: str | None = None
........................................................................ [ 15%]
........................................................................ [ 30%]
.................................................................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________

    def test_lifespan_startup_shutdown_configured():
        """Test app lifespan startup and shutdown when configured."""
        from stampbot.main import app
    
        # Use TestClient as context manager to trigger lifespan events
>       with TestClient(app) as client:
             ^^^^^^^^^^^^^^^

tests/test_main.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:602: in wait_startup
    message = await receive()
              ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:324: in __call__
    raw = Request(scope).headers.get(client_ip_header)
          ^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:219: in __init__
    super().__init__(scope)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x7c501656bdd0>
scope = {'type': 'lifespan', 'state': {}, 'app': <fastapi.applications.FastAPI object at 0x7c5016888ad0>}
receive = None

    def __init__(self, scope: Scope, receive: Receive | None = None) -> None:
>       assert scope["type"] in ("http", "websocket")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AssertionError

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:88: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/main.py:317
  /home/runner/work/stampbot/stampbot/stampbot/main.py:317: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if scope["type"] is not "http":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 209 passed, 3 deselected, 2 warnings in 1.60s
operator: core/ReplaceComparisonOperator_LtE_Eq, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 == metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x794f5fb80ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 == metrics_port <= 65535:
>               raise RuntimeError("metrics_port must be between 1 and 65535")
E               RuntimeError: metrics_port must be between 1 and 65535

stampbot/main.py:163: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:16:30.596812Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:16:30.597062Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:16:30.596812Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:16:30.597062Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must be between 1 and 65535
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.16s
operator: core/ReplaceComparisonOperator_LtE_Eq, occurrence: 1, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 <= metrics_port == 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x70ee4258cad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 <= metrics_port == 65535:
>               raise RuntimeError("metrics_port must be between 1 and 65535")
E               RuntimeError: metrics_port must be between 1 and 65535

stampbot/main.py:163: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:48:23.783320Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:48:23.783577Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:48:23.783320Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:48:23.783577Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must be between 1 and 65535
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.24s
operator: core/ReplaceComparisonOperator_LtE_NotEq, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 != metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......F
=================================== FAILURES ===================================
_ test_lifespan_rejects_invalid_metrics_listener[127.0.0.1-0-metrics_port must be between 1 and 65535] _

metrics_host = '127.0.0.1', metrics_port = 0
message = 'metrics_port must be between 1 and 65535'

    @pytest.mark.parametrize(
        ("metrics_host", "metrics_port", "message"),
        [
            ("", 9090, "metrics_host must not be empty"),
            ("127.0.0.1", 0, "metrics_port must be between 1 and 65535"),
            ("127.0.0.1", 65536, "metrics_port must be between 1 and 65535"),
        ],
    )
    def test_lifespan_rejects_invalid_metrics_listener(
        metrics_host: str,
        metrics_port: int,
        message: str,
    ):
        """Test that invalid dedicated-listener settings fail startup."""
        from stampbot.main import app
    
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = metrics_host
            mock_settings.metrics_port = metrics_port
    
>           with pytest.raises(RuntimeError, match=message):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE RuntimeError

tests/test_main.py:251: Failed
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:23:33.841912Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:23:33.842042Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"extra": {"host": "127.0.0.1", "port": 0}, "event": "Prometheus metrics listener started", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:23:33.842467Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "Shutting down stampbot", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:23:34.343235Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:23:33.841912Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:23:33.842042Z'}
INFO     stampbot.main:main.py:167 {'extra': {'host': '127.0.0.1', 'port': 0}, 'event': 'Prometheus metrics listener started', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:23:33.842467Z'}
INFO     stampbot.main:main.py:178 {'event': 'Shutting down stampbot', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:23:34.343235Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_rejects_invalid_metrics_listener[127.0.0.1-0-metrics_port must be between 1 and 65535] - Failed: DID NOT RAISE RuntimeError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 223 passed, 3 deselected, 1 warning in 2.60s
operator: core/ReplaceComparisonOperator_LtE_NotEq, occurrence: 1, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 <= metrics_port != 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........F
=================================== FAILURES ===================================
_ test_lifespan_rejects_invalid_metrics_listener[127.0.0.1-65536-metrics_port must be between 1 and 65535] _

metrics_host = '127.0.0.1', metrics_port = 65536
message = 'metrics_port must be between 1 and 65535'

    @pytest.mark.parametrize(
        ("metrics_host", "metrics_port", "message"),
        [
            ("", 9090, "metrics_host must not be empty"),
            ("127.0.0.1", 0, "metrics_port must be between 1 and 65535"),
            ("127.0.0.1", 65536, "metrics_port must be between 1 and 65535"),
        ],
    )
    def test_lifespan_rejects_invalid_metrics_listener(
        metrics_host: str,
        metrics_port: int,
        message: str,
    ):
        """Test that invalid dedicated-listener settings fail startup."""
        from stampbot.main import app
    
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = metrics_host
            mock_settings.metrics_port = metrics_port
    
            with pytest.raises(RuntimeError, match=message):
>               with TestClient(app):
                     ^^^^^^^^^^^^^^^

tests/test_main.py:252: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:166: in lifespan
    metrics_runtime = start_metrics_server(metrics_host, metrics_port)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/metrics.py:194: in start_metrics_server
    server, thread = start_http_server(port=port, addr=host, registry=registry)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/exposition.py:260: in start_wsgi_server
    httpd = make_server(addr, port, app, TmpServer, handler_class=_SilentHandler)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/wsgiref/simple_server.py:150: in make_server
    server = server_class((host, port), handler_class)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/socketserver.py:457: in __init__
    self.server_bind()
/usr/lib/python3.14/wsgiref/simple_server.py:50: in server_bind
    HTTPServer.server_bind(self)
/usr/lib/python3.14/http/server.py:148: in server_bind
    socketserver.TCPServer.server_bind(self)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <prometheus_client.exposition.start_wsgi_server.<locals>.TmpServer object at 0x7be10b7830e0>

    def server_bind(self):
        """Called by constructor to bind the socket.
    
        May be overridden.
    
        """
        if self.allow_reuse_address and hasattr(socket, "SO_REUSEADDR"):
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # Since Linux 6.12.9, SO_REUSEPORT is not allowed
        # on other address families than AF_INET/AF_INET6.
        if (
            self.allow_reuse_port and hasattr(socket, "SO_REUSEPORT")
            and self.address_family in (socket.AF_INET, socket.AF_INET6)
        ):
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
>       self.socket.bind(self.server_address)
E       OverflowError: bind(): port must be 0-65535.

/usr/lib/python3.14/socketserver.py:478: OverflowError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:31:51.865967Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:31:51.866095Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:31:51.865967Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:31:51.866095Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_rejects_invalid_metrics_listener[127.0.0.1-65536-metrics_port must be between 1 and 65535] - OverflowError: bind(): port must be 0-65535.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 224 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceComparisonOperator_LtE_Lt, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 < metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_LtE_Lt, occurrence: 1, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 <= metrics_port < 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceComparisonOperator_LtE_Gt, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 > metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x7c6774e68ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 > metrics_port <= 65535:
>               raise RuntimeError("metrics_port must be between 1 and 65535")
E               RuntimeError: metrics_port must be between 1 and 65535

stampbot/main.py:163: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:18:41.062926Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:18:41.063202Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:18:41.062926Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:18:41.063202Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must be between 1 and 65535
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.21s
operator: core/ReplaceComparisonOperator_LtE_Gt, occurrence: 1, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 <= metrics_port > 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x78513b784ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 <= metrics_port > 65535:
>               raise RuntimeError("metrics_port must be between 1 and 65535")
E               RuntimeError: metrics_port must be between 1 and 65535

stampbot/main.py:163: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:15:48.555038Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:15:48.555323Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:15:48.555038Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:15:48.555323Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must be between 1 and 65535
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.20s
operator: core/ReplaceComparisonOperator_LtE_GtE, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 >= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x715d63898ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 >= metrics_port <= 65535:
>               raise RuntimeError("metrics_port must be between 1 and 65535")
E               RuntimeError: metrics_port must be between 1 and 65535

stampbot/main.py:163: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:25:40.534918Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:25:40.535156Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:25:40.534918Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:25:40.535156Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must be between 1 and 65535
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.20s
operator: core/ReplaceComparisonOperator_LtE_GtE, occurrence: 1, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 <= metrics_port >= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x75c4d5f7cad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 <= metrics_port >= 65535:
>               raise RuntimeError("metrics_port must be between 1 and 65535")
E               RuntimeError: metrics_port must be between 1 and 65535

stampbot/main.py:163: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:55:55.070293Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:55:55.070552Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:55:55.070293Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:55:55.070552Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must be between 1 and 65535
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.17s
operator: core/ReplaceComparisonOperator_LtE_Is, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 is metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x73d22e494ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 is metrics_port <= 65535:
>               raise RuntimeError("metrics_port must be between 1 and 65535")
E               RuntimeError: metrics_port must be between 1 and 65535

stampbot/main.py:163: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:26:26.002005Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:26:26.002274Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:26:26.002005Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:26:26.002274Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/main.py:162
  /home/runner/work/stampbot/stampbot/stampbot/main.py:162: SyntaxWarning: "is" with 'int' literal. Did you mean "=="?
    if not 1 is metrics_port <= 65535:

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must be between 1 and 65535
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 2 warnings in 2.18s
operator: core/ReplaceComparisonOperator_LtE_IsNot, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 is not metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......F
=================================== FAILURES ===================================
_ test_lifespan_rejects_invalid_metrics_listener[127.0.0.1-0-metrics_port must be between 1 and 65535] _

metrics_host = '127.0.0.1', metrics_port = 0
message = 'metrics_port must be between 1 and 65535'

    @pytest.mark.parametrize(
        ("metrics_host", "metrics_port", "message"),
        [
            ("", 9090, "metrics_host must not be empty"),
            ("127.0.0.1", 0, "metrics_port must be between 1 and 65535"),
            ("127.0.0.1", 65536, "metrics_port must be between 1 and 65535"),
        ],
    )
    def test_lifespan_rejects_invalid_metrics_listener(
        metrics_host: str,
        metrics_port: int,
        message: str,
    ):
        """Test that invalid dedicated-listener settings fail startup."""
        from stampbot.main import app
    
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = metrics_host
            mock_settings.metrics_port = metrics_port
    
>           with pytest.raises(RuntimeError, match=message):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE RuntimeError

tests/test_main.py:251: Failed
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:37:20.020507Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:37:20.020647Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"extra": {"host": "127.0.0.1", "port": 0}, "event": "Prometheus metrics listener started", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:37:20.021021Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "Shutting down stampbot", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:37:20.521860Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:37:20.020507Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:37:20.020647Z'}
INFO     stampbot.main:main.py:167 {'extra': {'host': '127.0.0.1', 'port': 0}, 'event': 'Prometheus metrics listener started', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:37:20.021021Z'}
INFO     stampbot.main:main.py:178 {'event': 'Shutting down stampbot', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:37:20.521860Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/main.py:162
  /home/runner/work/stampbot/stampbot/stampbot/main.py:162: SyntaxWarning: "is not" with 'int' literal. Did you mean "!="?
    if not 1 is not metrics_port <= 65535:

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_rejects_invalid_metrics_listener[127.0.0.1-0-metrics_port must be between 1 and 65535] - Failed: DID NOT RAISE RuntimeError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 223 passed, 3 deselected, 2 warnings in 2.62s
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 0, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -417,7 +417,7 @@
     """
     if not raw:
         return None
-    if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
+    if len(raw) == MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
     return raw
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
............................F
=================================== FAILURES ===================================
_ test_webhook_drops_malformed_delivery_id[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] _

test_client = <starlette.testclient.TestClient object at 0x79eff2d4f130>
header = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

    @pytest.mark.parametrize("header", ["", "x" * 65, "not a guid!", "../../etc"])
    def test_webhook_drops_malformed_delivery_id(test_client: TestClient, header: str):
        """Test a delivery header that is not a GUID is neither logged nor forwarded."""
        body = json.dumps({"zen": "test"}).encode()
        seen_context: dict[str, object] = {}
    
        async def fake_handle(event_type, payload, delivery_id=None):
            seen_context.update(structlog.contextvars.get_contextvars())
            return {"status": "ok", "message": event_type, "delivery": delivery_id}
    
        with patch("stampbot.main.webhook_handler.handle_event", side_effect=fake_handle):
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-GitHub-Delivery": header,
                    "X-Hub-Signature-256": _signed_ping(body),
                },
            )
    
        assert response.status_code == 200
>       assert response.json()["delivery"] is None
E       AssertionError: assert 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' is None

tests/test_main.py:719: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:23:56.719783Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_drops_malformed_delivery_id[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] - AssertionError: assert 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 244 passed, 3 deselected, 1 warning in 2.22s
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 1, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) + len(chunk) == MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..................F
=================================== FAILURES ===================================
_______________ test_webhook_streamed_body_at_limit_is_processed _______________

    async def test_webhook_streamed_body_at_limit_is_processed():
        """A streamed body of exactly the limit is read and handled normally."""
        payload = _payload_of_exactly(MAX_WEBHOOK_BODY_SIZE)
        chunk_size = 64 * 1024
    
        async def body():
            for start in range(0, len(payload), chunk_size):
                yield payload[start : start + chunk_size]
    
        with patch.object(webhook_handler, "verify_signature", return_value=True):
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
>       assert response.status_code == 200
E       assert 413 == 200
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:497: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:38:12.065000Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_at_limit_is_processed - assert 413 == 200
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 234 passed, 3 deselected, 1 warning in 2.16s
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 2, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -497,7 +497,7 @@
 
     # Refuse a declared-oversize body without reading any of it.
     content_length = request.headers.get("content-length")
-    if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+    if content_length and int(content_length) == MAX_WEBHOOK_BODY_SIZE:
         errors_total.labels(error_type="payload_too_large").inc()
         raise HTTPException(status_code=413, detail="Request body too large")
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________

test_client = <starlette.testclient.TestClient object at 0x757face839b0>

    def test_webhook_content_length_too_large(test_client: TestClient):
        """A declared-oversize body is refused without reading it."""
        response = test_client.post(
            "/webhook",
            content=b"x",  # Small actual body
            headers={
                "Content-Type": "application/json",
                "Content-Length": str(MAX_WEBHOOK_BODY_SIZE + 1),
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=fake",
            },
        )
>       assert response.status_code == 413
E       assert 401 == 413
E        +  where 401 = <Response [401 Unauthorized]>.status_code

tests/test_main.py:383: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Invalid webhook signature", "client_ip": "testclient", "logger": "stampbot.main", "level": "warning", "timestamp": "2026-11-04T03:38:39.197051Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 401 Unauthorized\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:38:39.197555Z"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.main:main.py:512 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'warning', 'timestamp': '2026-11-04T03:38:39.197051Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 401 == 413
 +  where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 231 passed, 3 deselected, 1 warning in 2.15s
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 0, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -417,7 +417,7 @@
     """
     if not raw:
         return None
-    if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
+    if len(raw) != MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
     return raw
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..........................F
=================================== FAILURES ===================================
__________________ test_webhook_passes_delivery_id_to_handler __________________

test_client = <starlette.testclient.TestClient object at 0x7d55ba16f9b0>

    def test_webhook_passes_delivery_id_to_handler(test_client: TestClient):
        """Test the GitHub delivery GUID reaches the handler and the log context."""
        body = json.dumps({"zen": "test"}).encode()
        delivery = "72d3162e-cc78-11e3-81ab-4c9367dc0958"
        seen_context: dict[str, object] = {}
    
        async def fake_handle(event_type, payload, delivery_id=None):
            seen_context.update(structlog.contextvars.get_contextvars())
            return {"status": "ok", "message": event_type, "delivery": delivery_id}
    
        with patch("stampbot.main.webhook_handler.handle_event", side_effect=fake_handle):
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-GitHub-Delivery": delivery,
                    "X-Hub-Signature-256": _signed_ping(body),
                },
            )
    
        assert response.status_code == 200
>       assert response.json()["delivery"] == delivery
E       AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'

tests/test_main.py:692: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:58:52.616849Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_passes_delivery_id_to_handler - AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 242 passed, 3 deselected, 1 warning in 2.14s
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 1, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) + len(chunk) != MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7bf3a6d8e9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:05:57.647580Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.11s
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 2, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -497,7 +497,7 @@
 
     # Refuse a declared-oversize body without reading any of it.
     content_length = request.headers.get("content-length")
-    if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+    if content_length and int(content_length) != MAX_WEBHOOK_BODY_SIZE:
         errors_total.labels(error_type="payload_too_large").inc()
         raise HTTPException(status_code=413, detail="Request body too large")
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7070c317e9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:12:34.857471Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.10s
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 0, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -417,7 +417,7 @@
     """
     if not raw:
         return None
-    if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
+    if len(raw) < MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
     return raw
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..........................F
=================================== FAILURES ===================================
__________________ test_webhook_passes_delivery_id_to_handler __________________

test_client = <starlette.testclient.TestClient object at 0x7b010c1239b0>

    def test_webhook_passes_delivery_id_to_handler(test_client: TestClient):
        """Test the GitHub delivery GUID reaches the handler and the log context."""
        body = json.dumps({"zen": "test"}).encode()
        delivery = "72d3162e-cc78-11e3-81ab-4c9367dc0958"
        seen_context: dict[str, object] = {}
    
        async def fake_handle(event_type, payload, delivery_id=None):
            seen_context.update(structlog.contextvars.get_contextvars())
            return {"status": "ok", "message": event_type, "delivery": delivery_id}
    
        with patch("stampbot.main.webhook_handler.handle_event", side_effect=fake_handle):
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-GitHub-Delivery": delivery,
                    "X-Hub-Signature-256": _signed_ping(body),
                },
            )
    
        assert response.status_code == 200
>       assert response.json()["delivery"] == delivery
E       AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'

tests/test_main.py:692: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:10:33.183741Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_passes_delivery_id_to_handler - AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 242 passed, 3 deselected, 1 warning in 2.18s
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 1, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) + len(chunk) < MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7c029e2ae9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:47:01.565487Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.15s
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 2, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -497,7 +497,7 @@
 
     # Refuse a declared-oversize body without reading any of it.
     content_length = request.headers.get("content-length")
-    if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+    if content_length and int(content_length) < MAX_WEBHOOK_BODY_SIZE:
         errors_total.labels(error_type="payload_too_large").inc()
         raise HTTPException(status_code=413, detail="Request body too large")
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7b3dd555a9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:13:27.131605Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.08s
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 0, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -417,7 +417,7 @@
     """
     if not raw:
         return None
-    if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
+    if len(raw) <= MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
     return raw
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..........................F
=================================== FAILURES ===================================
__________________ test_webhook_passes_delivery_id_to_handler __________________

test_client = <starlette.testclient.TestClient object at 0x77d1c75739b0>

    def test_webhook_passes_delivery_id_to_handler(test_client: TestClient):
        """Test the GitHub delivery GUID reaches the handler and the log context."""
        body = json.dumps({"zen": "test"}).encode()
        delivery = "72d3162e-cc78-11e3-81ab-4c9367dc0958"
        seen_context: dict[str, object] = {}
    
        async def fake_handle(event_type, payload, delivery_id=None):
            seen_context.update(structlog.contextvars.get_contextvars())
            return {"status": "ok", "message": event_type, "delivery": delivery_id}
    
        with patch("stampbot.main.webhook_handler.handle_event", side_effect=fake_handle):
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-GitHub-Delivery": delivery,
                    "X-Hub-Signature-256": _signed_ping(body),
                },
            )
    
        assert response.status_code == 200
>       assert response.json()["delivery"] == delivery
E       AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'

tests/test_main.py:692: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:41:56.429120Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_passes_delivery_id_to_handler - AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 242 passed, 3 deselected, 1 warning in 2.24s
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 1, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) + len(chunk) <= MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x71edeef6e9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:58:49.392285Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.07s
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 2, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -497,7 +497,7 @@
 
     # Refuse a declared-oversize body without reading any of it.
     content_length = request.headers.get("content-length")
-    if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+    if content_length and int(content_length) <= MAX_WEBHOOK_BODY_SIZE:
         errors_total.labels(error_type="payload_too_large").inc()
         raise HTTPException(status_code=413, detail="Request body too large")
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7ba09e9869c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:36:41.818249Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.10s
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 0, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -417,7 +417,7 @@
     """
     if not raw:
         return None
-    if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
+    if len(raw) >= MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
     return raw
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 1, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) + len(chunk) >= MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..................F
=================================== FAILURES ===================================
_______________ test_webhook_streamed_body_at_limit_is_processed _______________

    async def test_webhook_streamed_body_at_limit_is_processed():
        """A streamed body of exactly the limit is read and handled normally."""
        payload = _payload_of_exactly(MAX_WEBHOOK_BODY_SIZE)
        chunk_size = 64 * 1024
    
        async def body():
            for start in range(0, len(payload), chunk_size):
                yield payload[start : start + chunk_size]
    
        with patch.object(webhook_handler, "verify_signature", return_value=True):
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
>       assert response.status_code == 200
E       assert 413 == 200
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:497: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:20:10.472512Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_at_limit_is_processed - assert 413 == 200
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 234 passed, 3 deselected, 1 warning in 2.17s
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 2, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -497,7 +497,7 @@
 
     # Refuse a declared-oversize body without reading any of it.
     content_length = request.headers.get("content-length")
-    if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+    if content_length and int(content_length) >= MAX_WEBHOOK_BODY_SIZE:
         errors_total.labels(error_type="payload_too_large").inc()
         raise HTTPException(status_code=413, detail="Request body too large")
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...................F
=================================== FAILURES ===================================
______________ test_webhook_content_length_at_limit_is_processed _______________

test_client = <starlette.testclient.TestClient object at 0x75c2eded8380>

    def test_webhook_content_length_at_limit_is_processed(test_client: TestClient):
        """A declared body of exactly the limit passes the pre-read check."""
        payload = _payload_of_exactly(MAX_WEBHOOK_BODY_SIZE)
    
        with patch.object(webhook_handler, "verify_signature", return_value=True):
            response = test_client.post(
                "/webhook",
                content=payload,  # httpx sets Content-Length from the byte length.
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-Hub-Signature-256": "sha256=fake",
                },
            )
    
>       assert response.status_code == 200
E       assert 413 == 200
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:516: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:17:28.073711Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_at_limit_is_processed - assert 413 == 200
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 235 passed, 3 deselected, 1 warning in 2.15s
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 0, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -417,7 +417,7 @@
     """
     if not raw:
         return None
-    if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
+    if len(raw) is MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
     return raw
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
............................F
=================================== FAILURES ===================================
_ test_webhook_drops_malformed_delivery_id[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] _

test_client = <starlette.testclient.TestClient object at 0x768abf2cf130>
header = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

    @pytest.mark.parametrize("header", ["", "x" * 65, "not a guid!", "../../etc"])
    def test_webhook_drops_malformed_delivery_id(test_client: TestClient, header: str):
        """Test a delivery header that is not a GUID is neither logged nor forwarded."""
        body = json.dumps({"zen": "test"}).encode()
        seen_context: dict[str, object] = {}
    
        async def fake_handle(event_type, payload, delivery_id=None):
            seen_context.update(structlog.contextvars.get_contextvars())
            return {"status": "ok", "message": event_type, "delivery": delivery_id}
    
        with patch("stampbot.main.webhook_handler.handle_event", side_effect=fake_handle):
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-GitHub-Delivery": header,
                    "X-Hub-Signature-256": _signed_ping(body),
                },
            )
    
        assert response.status_code == 200
>       assert response.json()["delivery"] is None
E       AssertionError: assert 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' is None

tests/test_main.py:719: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:43:08.790092Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_drops_malformed_delivery_id[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] - AssertionError: assert 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 244 passed, 3 deselected, 1 warning in 2.18s
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 1, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) + len(chunk) is MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................F
=================================== FAILURES ===================================
_______ test_webhook_streamed_body_over_limit_is_rejected_while_reading ________

    async def test_webhook_streamed_body_over_limit_is_rejected_while_reading():
        """An oversize streamed body is refused during the read, not after it.
    
        Without a ``Content-Length`` the pre-read check cannot see the size, so this
        is the case that used to reach ``Request.body()`` and buffer everything. The
        offered-bytes assertion is the regression guard: it fails if the limit moves
        back to a check that runs once the whole body is already in memory.
        """
        chunk_size = 64 * 1024
        total = 4 * MAX_WEBHOOK_BODY_SIZE
        offered = 0
    
        async def body():
            nonlocal offered
            for _ in range(total // chunk_size):
                offered += chunk_size
                yield b"x" * chunk_size
    
        with patch.object(webhook_handler, "verify_signature", return_value=True) as verify:
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
>       assert response.status_code == 413
E       assert 400 == 413
E        +  where 400 = <Response [400 Bad Request]>.status_code

tests/test_main.py:437: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)", "client_ip": "127.0.0.1", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T03:51:47.028953Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 400 Bad Request\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:51:47.029423Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:523 {'event': 'Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)', 'client_ip': '127.0.0.1', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T03:51:47.028953Z'}
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_over_limit_is_rejected_while_reading - assert 400 == 413
 +  where 400 = <Response [400 Bad Request]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 232 passed, 3 deselected, 1 warning in 2.16s
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 2, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -497,7 +497,7 @@
 
     # Refuse a declared-oversize body without reading any of it.
     content_length = request.headers.get("content-length")
-    if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+    if content_length and int(content_length) is MAX_WEBHOOK_BODY_SIZE:
         errors_total.labels(error_type="payload_too_large").inc()
         raise HTTPException(status_code=413, detail="Request body too large")
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________

test_client = <starlette.testclient.TestClient object at 0x7adcc227b9b0>

    def test_webhook_content_length_too_large(test_client: TestClient):
        """A declared-oversize body is refused without reading it."""
        response = test_client.post(
            "/webhook",
            content=b"x",  # Small actual body
            headers={
                "Content-Type": "application/json",
                "Content-Length": str(MAX_WEBHOOK_BODY_SIZE + 1),
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=fake",
            },
        )
>       assert response.status_code == 413
E       assert 401 == 413
E        +  where 401 = <Response [401 Unauthorized]>.status_code

tests/test_main.py:383: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Invalid webhook signature", "client_ip": "testclient", "logger": "stampbot.main", "level": "warning", "timestamp": "2026-11-04T04:25:06.915345Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 401 Unauthorized\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:25:06.915808Z"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.main:main.py:512 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'warning', 'timestamp': '2026-11-04T04:25:06.915345Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 401 == 413
 +  where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 231 passed, 3 deselected, 1 warning in 2.10s
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 0, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -417,7 +417,7 @@
     """
     if not raw:
         return None
-    if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
+    if len(raw) is not MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
     return raw
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..........................F
=================================== FAILURES ===================================
__________________ test_webhook_passes_delivery_id_to_handler __________________

test_client = <starlette.testclient.TestClient object at 0x79fc4a1f39b0>

    def test_webhook_passes_delivery_id_to_handler(test_client: TestClient):
        """Test the GitHub delivery GUID reaches the handler and the log context."""
        body = json.dumps({"zen": "test"}).encode()
        delivery = "72d3162e-cc78-11e3-81ab-4c9367dc0958"
        seen_context: dict[str, object] = {}
    
        async def fake_handle(event_type, payload, delivery_id=None):
            seen_context.update(structlog.contextvars.get_contextvars())
            return {"status": "ok", "message": event_type, "delivery": delivery_id}
    
        with patch("stampbot.main.webhook_handler.handle_event", side_effect=fake_handle):
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-GitHub-Delivery": delivery,
                    "X-Hub-Signature-256": _signed_ping(body),
                },
            )
    
        assert response.status_code == 200
>       assert response.json()["delivery"] == delivery
E       AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'

tests/test_main.py:692: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:47:57.248023Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_passes_delivery_id_to_handler - AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 242 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 1, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if len(body) + len(chunk) is not MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x775ac67829c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:17:15.959597Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.11s
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 2, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -497,7 +497,7 @@
 
     # Refuse a declared-oversize body without reading any of it.
     content_length = request.headers.get("content-length")
-    if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+    if content_length and int(content_length) is not MAX_WEBHOOK_BODY_SIZE:
         errors_total.labels(error_type="payload_too_large").inc()
         raise HTTPException(status_code=413, detail="Request body too large")
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7227a1386ad0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:24:16.975864Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.13s
operator: core/ReplaceComparisonOperator_Is_Eq, occurrence: 0, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -219,7 +219,7 @@
         route_path = getattr(matched_route, "path", None)
         if not isinstance(route_path, str):
             continue
-        if match is Match.FULL:
+        if match == Match.FULL:
             return route_path
         if match is Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceComparisonOperator_Is_Eq, occurrence: 1, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -221,7 +221,7 @@
             continue
         if match is Match.FULL:
             return route_path
-        if match is Match.PARTIAL and partial_match is None:
+        if match == Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
             partial_match = route_path
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceComparisonOperator_Is_NotEq, occurrence: 0, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -219,7 +219,7 @@
         route_path = getattr(matched_route, "path", None)
         if not isinstance(route_path, str):
             continue
-        if match is Match.FULL:
+        if match != Match.FULL:
             return route_path
         if match is Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x73cf352e9fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='127333787674880'>, 'http_request_duration_seconds': ...='127333787675552'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='127333787675888'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
            response = test_client.post(path, content=b"request body")
            assert response.status_code == 404
    
>       assert _endpoint_labels(http_metric_spies["http_requests_total"]) == [
            UNMATCHED_ENDPOINT,
            UNMATCHED_ENDPOINT,
        ]
E       AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
E         
E         At index 0 diff: '/openapi.json' != 'unmatched'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         -     'unmatched',
E         -     'unmatched',
E         +     '/openapi.json',
E         +     '/openapi.json',
E           ]

tests/test_http_metrics.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:07:51.928536Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found" [httpx]
2026-11-04T04:07:51.931124Z [info     ] HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found"
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
  
  At index 0 diff: '/openapi.json' != 'unmatched'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     'unmatched',
  -     'unmatched',
  +     '/openapi.json',
  +     '/openapi.json',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceComparisonOperator_Is_NotEq, occurrence: 1, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -221,7 +221,7 @@
             continue
         if match is Match.FULL:
             return route_path
-        if match is Match.PARTIAL and partial_match is None:
+        if match != Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
             partial_match = route_path
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x75abc08f9fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='129380530497792'>, 'http_request_duration_seconds': ...='129380530498464'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='129380530498800'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
            response = test_client.post(path, content=b"request body")
            assert response.status_code == 404
    
>       assert _endpoint_labels(http_metric_spies["http_requests_total"]) == [
            UNMATCHED_ENDPOINT,
            UNMATCHED_ENDPOINT,
        ]
E       AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
E         
E         At index 0 diff: '/openapi.json' != 'unmatched'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         -     'unmatched',
E         -     'unmatched',
E         +     '/openapi.json',
E         +     '/openapi.json',
E           ]

tests/test_http_metrics.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:23:47.154949Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found" [httpx]
2026-11-04T03:23:47.157697Z [info     ] HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found"
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
  
  At index 0 diff: '/openapi.json' != 'unmatched'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     'unmatched',
  -     'unmatched',
  +     '/openapi.json',
  +     '/openapi.json',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.49s
operator: core/ReplaceComparisonOperator_Is_Lt, occurrence: 0, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -219,7 +219,7 @@
         route_path = getattr(matched_route, "path", None)
         if not isinstance(route_path, str):
             continue
-        if match is Match.FULL:
+        if match < Match.FULL:
             return route_path
         if match is Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x739ac60f9fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='127108585072896'>, 'http_request_duration_seconds': ...='127108585073568'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='127108585073904'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:243: in metrics_middleware
    endpoint = _metric_endpoint_label(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x739ac5f3c980>

    def _metric_endpoint_label(request: Request) -> str:
        """Return a bounded Prometheus endpoint label for a request.
    
        Route templates are defined by the application, so labels such as
        ``/widgets/{widget_id}`` have bounded cardinality even when the path
        parameter is attacker controlled. Requests that do not match a registered
        route share one fallback label instead of exposing their raw URL paths.
    
        Args:
            request: Incoming HTTP request.
    
        Returns:
            The matched route template or the unmatched-route fallback.
        """
        current_route = request.scope.get("route")
        current_path = getattr(current_route, "path", None)
        if isinstance(current_path, str):
            return current_path
    
        partial_match: str | None = None
        for route in request.app.routes:
            match, child_scope = route.matches(request.scope)
            matched_route = child_scope.get("route", route)
            route_path = getattr(matched_route, "path", None)
            if not isinstance(route_path, str):
                continue
>           if match < Match.FULL:
               ^^^^^^^^^^^^^^^^^^
E           TypeError: '<' not supported between instances of 'Match' and 'Match'

stampbot/main.py:222: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:36:44.418306Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: '<' not supported between instances of 'Match' and 'Match'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceComparisonOperator_Is_Lt, occurrence: 1, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -221,7 +221,7 @@
             continue
         if match is Match.FULL:
             return route_path
-        if match is Match.PARTIAL and partial_match is None:
+        if match < Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
             partial_match = route_path
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x70df56cfdfd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='124104536484096'>, 'http_request_duration_seconds': ...='124104536484768'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='124104536485104'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:243: in metrics_middleware
    endpoint = _metric_endpoint_label(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x70df56b40980>

    def _metric_endpoint_label(request: Request) -> str:
        """Return a bounded Prometheus endpoint label for a request.
    
        Route templates are defined by the application, so labels such as
        ``/widgets/{widget_id}`` have bounded cardinality even when the path
        parameter is attacker controlled. Requests that do not match a registered
        route share one fallback label instead of exposing their raw URL paths.
    
        Args:
            request: Incoming HTTP request.
    
        Returns:
            The matched route template or the unmatched-route fallback.
        """
        current_route = request.scope.get("route")
        current_path = getattr(current_route, "path", None)
        if isinstance(current_path, str):
            return current_path
    
        partial_match: str | None = None
        for route in request.app.routes:
            match, child_scope = route.matches(request.scope)
            matched_route = child_scope.get("route", route)
            route_path = getattr(matched_route, "path", None)
            if not isinstance(route_path, str):
                continue
            if match is Match.FULL:
                return route_path
>           if match < Match.PARTIAL and partial_match is None:
               ^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '<' not supported between instances of 'Match' and 'Match'

stampbot/main.py:224: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:31:25.570316Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: '<' not supported between instances of 'Match' and 'Match'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.56s
operator: core/ReplaceComparisonOperator_Is_LtE, occurrence: 0, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -219,7 +219,7 @@
         route_path = getattr(matched_route, "path", None)
         if not isinstance(route_path, str):
             continue
-        if match is Match.FULL:
+        if match <= Match.FULL:
             return route_path
         if match is Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x75d6ec501fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='129565948127488'>, 'http_request_duration_seconds': ...='129565948128160'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='129565948128496'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:243: in metrics_middleware
    endpoint = _metric_endpoint_label(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x75d6ec544980>

    def _metric_endpoint_label(request: Request) -> str:
        """Return a bounded Prometheus endpoint label for a request.
    
        Route templates are defined by the application, so labels such as
        ``/widgets/{widget_id}`` have bounded cardinality even when the path
        parameter is attacker controlled. Requests that do not match a registered
        route share one fallback label instead of exposing their raw URL paths.
    
        Args:
            request: Incoming HTTP request.
    
        Returns:
            The matched route template or the unmatched-route fallback.
        """
        current_route = request.scope.get("route")
        current_path = getattr(current_route, "path", None)
        if isinstance(current_path, str):
            return current_path
    
        partial_match: str | None = None
        for route in request.app.routes:
            match, child_scope = route.matches(request.scope)
            matched_route = child_scope.get("route", route)
            route_path = getattr(matched_route, "path", None)
            if not isinstance(route_path, str):
                continue
>           if match <= Match.FULL:
               ^^^^^^^^^^^^^^^^^^^
E           TypeError: '<=' not supported between instances of 'Match' and 'Match'

stampbot/main.py:222: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:43:42.083556Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: '<=' not supported between instances of 'Match' and 'Match'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.54s
operator: core/ReplaceComparisonOperator_Is_LtE, occurrence: 1, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -221,7 +221,7 @@
             continue
         if match is Match.FULL:
             return route_path
-        if match is Match.PARTIAL and partial_match is None:
+        if match <= Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
             partial_match = route_path
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x749624de1fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='128188212455680'>, 'http_request_duration_seconds': ...='128188212456352'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='128188212456688'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:243: in metrics_middleware
    endpoint = _metric_endpoint_label(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x749624c24980>

    def _metric_endpoint_label(request: Request) -> str:
        """Return a bounded Prometheus endpoint label for a request.
    
        Route templates are defined by the application, so labels such as
        ``/widgets/{widget_id}`` have bounded cardinality even when the path
        parameter is attacker controlled. Requests that do not match a registered
        route share one fallback label instead of exposing their raw URL paths.
    
        Args:
            request: Incoming HTTP request.
    
        Returns:
            The matched route template or the unmatched-route fallback.
        """
        current_route = request.scope.get("route")
        current_path = getattr(current_route, "path", None)
        if isinstance(current_path, str):
            return current_path
    
        partial_match: str | None = None
        for route in request.app.routes:
            match, child_scope = route.matches(request.scope)
            matched_route = child_scope.get("route", route)
            route_path = getattr(matched_route, "path", None)
            if not isinstance(route_path, str):
                continue
            if match is Match.FULL:
                return route_path
>           if match <= Match.PARTIAL and partial_match is None:
               ^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '<=' not supported between instances of 'Match' and 'Match'

stampbot/main.py:224: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:19:49.995177Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: '<=' not supported between instances of 'Match' and 'Match'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceComparisonOperator_Is_Gt, occurrence: 0, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -219,7 +219,7 @@
         route_path = getattr(matched_route, "path", None)
         if not isinstance(route_path, str):
             continue
-        if match is Match.FULL:
+        if match > Match.FULL:
             return route_path
         if match is Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x7d209e3ddfd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='137579047282944'>, 'http_request_duration_seconds': ...='137579047283616'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='137579047283952'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:243: in metrics_middleware
    endpoint = _metric_endpoint_label(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x7d209e220980>

    def _metric_endpoint_label(request: Request) -> str:
        """Return a bounded Prometheus endpoint label for a request.
    
        Route templates are defined by the application, so labels such as
        ``/widgets/{widget_id}`` have bounded cardinality even when the path
        parameter is attacker controlled. Requests that do not match a registered
        route share one fallback label instead of exposing their raw URL paths.
    
        Args:
            request: Incoming HTTP request.
    
        Returns:
            The matched route template or the unmatched-route fallback.
        """
        current_route = request.scope.get("route")
        current_path = getattr(current_route, "path", None)
        if isinstance(current_path, str):
            return current_path
    
        partial_match: str | None = None
        for route in request.app.routes:
            match, child_scope = route.matches(request.scope)
            matched_route = child_scope.get("route", route)
            route_path = getattr(matched_route, "path", None)
            if not isinstance(route_path, str):
                continue
>           if match > Match.FULL:
               ^^^^^^^^^^^^^^^^^^
E           TypeError: '>' not supported between instances of 'Match' and 'Match'

stampbot/main.py:222: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:08:24.388129Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: '>' not supported between instances of 'Match' and 'Match'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.56s
operator: core/ReplaceComparisonOperator_Is_Gt, occurrence: 1, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -221,7 +221,7 @@
             continue
         if match is Match.FULL:
             return route_path
-        if match is Match.PARTIAL and partial_match is None:
+        if match > Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
             partial_match = route_path
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x7ac66afedfd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='134992617203968'>, 'http_request_duration_seconds': ...='134992617204640'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='134992617204976'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:243: in metrics_middleware
    endpoint = _metric_endpoint_label(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x7ac66ae30980>

    def _metric_endpoint_label(request: Request) -> str:
        """Return a bounded Prometheus endpoint label for a request.
    
        Route templates are defined by the application, so labels such as
        ``/widgets/{widget_id}`` have bounded cardinality even when the path
        parameter is attacker controlled. Requests that do not match a registered
        route share one fallback label instead of exposing their raw URL paths.
    
        Args:
            request: Incoming HTTP request.
    
        Returns:
            The matched route template or the unmatched-route fallback.
        """
        current_route = request.scope.get("route")
        current_path = getattr(current_route, "path", None)
        if isinstance(current_path, str):
            return current_path
    
        partial_match: str | None = None
        for route in request.app.routes:
            match, child_scope = route.matches(request.scope)
            matched_route = child_scope.get("route", route)
            route_path = getattr(matched_route, "path", None)
            if not isinstance(route_path, str):
                continue
            if match is Match.FULL:
                return route_path
>           if match > Match.PARTIAL and partial_match is None:
               ^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '>' not supported between instances of 'Match' and 'Match'

stampbot/main.py:224: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:22:24.404077Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: '>' not supported between instances of 'Match' and 'Match'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.55s
operator: core/ReplaceComparisonOperator_Is_GtE, occurrence: 0, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -219,7 +219,7 @@
         route_path = getattr(matched_route, "path", None)
         if not isinstance(route_path, str):
             continue
-        if match is Match.FULL:
+        if match >= Match.FULL:
             return route_path
         if match is Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x7a0fe7005fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='134208718661888'>, 'http_request_duration_seconds': ...='134208718662560'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='134208718662896'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:243: in metrics_middleware
    endpoint = _metric_endpoint_label(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x7a0fe7048980>

    def _metric_endpoint_label(request: Request) -> str:
        """Return a bounded Prometheus endpoint label for a request.
    
        Route templates are defined by the application, so labels such as
        ``/widgets/{widget_id}`` have bounded cardinality even when the path
        parameter is attacker controlled. Requests that do not match a registered
        route share one fallback label instead of exposing their raw URL paths.
    
        Args:
            request: Incoming HTTP request.
    
        Returns:
            The matched route template or the unmatched-route fallback.
        """
        current_route = request.scope.get("route")
        current_path = getattr(current_route, "path", None)
        if isinstance(current_path, str):
            return current_path
    
        partial_match: str | None = None
        for route in request.app.routes:
            match, child_scope = route.matches(request.scope)
            matched_route = child_scope.get("route", route)
            route_path = getattr(matched_route, "path", None)
            if not isinstance(route_path, str):
                continue
>           if match >= Match.FULL:
               ^^^^^^^^^^^^^^^^^^^
E           TypeError: '>=' not supported between instances of 'Match' and 'Match'

stampbot/main.py:222: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:18:21.403846Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: '>=' not supported between instances of 'Match' and 'Match'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.57s
operator: core/ReplaceComparisonOperator_Is_GtE, occurrence: 1, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -221,7 +221,7 @@
             continue
         if match is Match.FULL:
             return route_path
-        if match is Match.PARTIAL and partial_match is None:
+        if match >= Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
             partial_match = route_path
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x730031d25fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='126444673067264'>, 'http_request_duration_seconds': ...='126444673067936'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='126444673068272'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:243: in metrics_middleware
    endpoint = _metric_endpoint_label(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x730031d68980>

    def _metric_endpoint_label(request: Request) -> str:
        """Return a bounded Prometheus endpoint label for a request.
    
        Route templates are defined by the application, so labels such as
        ``/widgets/{widget_id}`` have bounded cardinality even when the path
        parameter is attacker controlled. Requests that do not match a registered
        route share one fallback label instead of exposing their raw URL paths.
    
        Args:
            request: Incoming HTTP request.
    
        Returns:
            The matched route template or the unmatched-route fallback.
        """
        current_route = request.scope.get("route")
        current_path = getattr(current_route, "path", None)
        if isinstance(current_path, str):
            return current_path
    
        partial_match: str | None = None
        for route in request.app.routes:
            match, child_scope = route.matches(request.scope)
            matched_route = child_scope.get("route", route)
            route_path = getattr(matched_route, "path", None)
            if not isinstance(route_path, str):
                continue
            if match is Match.FULL:
                return route_path
>           if match >= Match.PARTIAL and partial_match is None:
               ^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: '>=' not supported between instances of 'Match' and 'Match'

stampbot/main.py:224: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:02:01.546010Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - TypeError: '>=' not supported between instances of 'Match' and 'Match'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.52s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 0, definition_name: _setup_is_available
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -85,7 +85,7 @@
     if not _setup_requested():
         return False
 
-    app_configured = is_configured() if configured is None else configured
+    app_configured = is_configured() if configured is not None else configured
     return not app_configured or _setup_allowed_when_configured()
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 1, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -219,7 +219,7 @@
         route_path = getattr(matched_route, "path", None)
         if not isinstance(route_path, str):
             continue
-        if match is Match.FULL:
+        if match is not Match.FULL:
             return route_path
         if match is Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x7de398505fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='138416466454784'>, 'http_request_duration_seconds': ...='138416466455456'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='138416466455792'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
            response = test_client.post(path, content=b"request body")
            assert response.status_code == 404
    
>       assert _endpoint_labels(http_metric_spies["http_requests_total"]) == [
            UNMATCHED_ENDPOINT,
            UNMATCHED_ENDPOINT,
        ]
E       AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
E         
E         At index 0 diff: '/openapi.json' != 'unmatched'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         -     'unmatched',
E         -     'unmatched',
E         +     '/openapi.json',
E         +     '/openapi.json',
E           ]

tests/test_http_metrics.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:23:49.767786Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found" [httpx]
2026-11-04T03:23:49.770743Z [info     ] HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found"
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
  
  At index 0 diff: '/openapi.json' != 'unmatched'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     'unmatched',
  -     'unmatched',
  +     '/openapi.json',
  +     '/openapi.json',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 2, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -221,7 +221,7 @@
             continue
         if match is Match.FULL:
             return route_path
-        if match is Match.PARTIAL and partial_match is None:
+        if match is not Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
             partial_match = route_path
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x73b7baa29fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='127232947431680'>, 'http_request_duration_seconds': ...='127232947432352'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='127232947432688'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
            response = test_client.post(path, content=b"request body")
            assert response.status_code == 404
    
>       assert _endpoint_labels(http_metric_spies["http_requests_total"]) == [
            UNMATCHED_ENDPOINT,
            UNMATCHED_ENDPOINT,
        ]
E       AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
E         
E         At index 0 diff: '/openapi.json' != 'unmatched'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         -     'unmatched',
E         -     'unmatched',
E         +     '/openapi.json',
E         +     '/openapi.json',
E           ]

tests/test_http_metrics.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:22:54.144989Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found" [httpx]
2026-11-04T03:22:54.147591Z [info     ] HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found"
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
  
  At index 0 diff: '/openapi.json' != 'unmatched'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     'unmatched',
  -     'unmatched',
  +     '/openapi.json',
  +     '/openapi.json',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.47s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 3, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -221,7 +221,7 @@
             continue
         if match is Match.FULL:
             return route_path
-        if match is Match.PARTIAL and partial_match is None:
+        if match is Match.PARTIAL and partial_match is not None:
             # A path match with the wrong method will become a 405 response.
             partial_match = route_path
 
........................................................................ [ 15%]
........................................................................ [ 30%]
................................................F
=================================== FAILURES ===================================
_____________ test_method_not_allowed_uses_matching_route_template _____________

routed_app = <fastapi.applications.FastAPI object at 0x79f9d910f620>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='134113995573968'>, 'http_request_duration_seconds': ...='134113995580688'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='134113995576320'>, ...}

    def test_method_not_allowed_uses_matching_route_template(
        routed_app: FastAPI,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """A 405 remains attributable to the bounded matching route."""
        client = TestClient(routed_app)
    
        response = client.get("/method-specific")
    
        assert response.status_code == 405
>       http_metric_spies["http_requests_total"].labels.assert_called_once_with(
            method="GET",
            endpoint="/method-specific",
            status=405,
        )

tests/test_http_metrics.py:161: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:998: in assert_called_once_with
    return self.assert_called_with(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='http_requests_total.labels' id='134113995583376'>
args = ()
kwargs = {'method': 'GET', 'endpoint': '/method-specific', 'status': 405}

    def assert_called_with(self, /, *args, **kwargs):
        """assert that the last call was made with the specified arguments.
    
        Raises an AssertionError if the args and keyword args passed in are
        different to the last call to the mock."""
        if self.call_args is None:
            expected = self._format_mock_call_signature(args, kwargs)
            actual = 'not called.'
            error_message = ('expected call not found.\nExpected: %s\n  Actual: %s'
                    % (expected, actual))
            raise AssertionError(error_message)
    
        def _error_message():
            msg = self._format_mock_failure_message(args, kwargs)
            return msg
        expected = self._call_matcher(_Call((args, kwargs), two=True))
        actual = self._call_matcher(self.call_args)
        if actual != expected:
            cause = expected if isinstance(expected, Exception) else None
>           raise AssertionError(_error_message()) from cause
E           AssertionError: expected call not found.
E           Expected: labels(method='GET', endpoint='/method-specific', status=405)
E             Actual: labels(method='GET', endpoint='unmatched', status=405)

/usr/lib/python3.14/unittest/mock.py:986: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:03:37.035912Z [info     ] HTTP Request: GET http://testserver/method-specific "HTTP/1.1 405 Method Not Allowed" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/method-specific "HTTP/1.1 405 Method Not Allowed"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_method_not_allowed_uses_matching_route_template - AssertionError: expected call not found.
Expected: labels(method='GET', endpoint='/method-specific', status=405)
  Actual: labels(method='GET', endpoint='unmatched', status=405)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 192 passed, 3 deselected, 1 warning in 1.54s
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 0, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -172,7 +172,7 @@
     try:
         yield
     finally:
-        if metrics_runtime is not None:
+        if metrics_runtime is None:
             stop_metrics_server(*metrics_runtime)
         # Shutdown
         logger.info("Shutting down stampbot")
........................................................................ [ 15%]
........................................................................ [ 30%]
.................................................................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________

    def test_lifespan_startup_shutdown_configured():
        """Test app lifespan startup and shutdown when configured."""
        from stampbot.main import app
    
        # Use TestClient as context manager to trigger lifespan events
>       with TestClient(app) as client:
             ^^^^^^^^^^^^^^^

tests/test_main.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:584: in __exit__
    self.exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:482: in _exit_wrapper
    callback(*args, **kwds)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:576: in wait_shutdown
    portal.call(self.wait_shutdown)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:624: in wait_shutdown
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:614: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:221: in __aexit__
    await anext(self.gen)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x78bf07c88ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 <= metrics_port <= 65535:
                raise RuntimeError("metrics_port must be between 1 and 65535")
            if metrics_port == int(settings.port):
                raise RuntimeError("metrics_port must differ from the public HTTP port")
            metrics_runtime = start_metrics_server(metrics_host, metrics_port)
            logger.info(
                "Prometheus metrics listener started",
                extra={"host": metrics_host, "port": metrics_port},
            )
    
        try:
            yield
        finally:
            if metrics_runtime is None:
>               stop_metrics_server(*metrics_runtime)
E               TypeError: stampbot.metrics.stop_metrics_server() argument after * must be an iterable, not NoneType

stampbot/main.py:176: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:22:02.391753Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:22:02.391889Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "HTTP Request: GET http://testserver/health \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:22:02.393375Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:22:02.391753Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:22:02.391889Z'}
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - TypeError: stampbot.metrics.stop_metrics_server() argument after * must be an iterable, not NoneType
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 209 passed, 3 deselected, 1 warning in 1.63s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0, definition_name: _setup_is_available
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -82,7 +82,7 @@
 
 def _setup_is_available(configured: bool | None = None) -> bool:
     """Return whether setup routes should be available for the current state."""
-    if not _setup_requested():
+    if  _setup_requested():
         return False
 
     app_configured = is_configured() if configured is None else configured
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
F
=================================== FAILURES ===================================
________________ test_ready_endpoint_unconfigured_setup_enabled ________________

test_client = <starlette.testclient.TestClient object at 0x795d83199260>

    def test_ready_endpoint_unconfigured_setup_enabled(test_client: TestClient):
        """Test readiness is 200 in setup mode so /setup stays reachable.
    
        An unconfigured pod with setup mode on must stay in the Service endpoints,
        otherwise the operator can never reach /setup to configure it.
        """
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.get.side_effect = lambda key, default=None: {
                "setup_enabled": True,
                "setup_allow_configured": False,
            }.get(key, default)
            response = test_client.get("/ready")
>       assert response.status_code == 200
E       assert 503 == 200
E        +  where 503 = <Response [503 Service Unavailable]>.status_code

tests/test_main.py:128: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 503 Service Unavailable\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:27:36.300371Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 503 Service Unavailable"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_unconfigured_setup_enabled - assert 503 == 200
 +  where 503 = <Response [503 Service Unavailable]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 216 passed, 3 deselected, 1 warning in 1.58s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1, definition_name: _setup_is_available
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -86,7 +86,7 @@
         return False
 
     app_configured = is_configured() if configured is None else configured
-    return not app_configured or _setup_allowed_when_configured()
+    return  app_configured or _setup_allowed_when_configured()
 
 
 def _require_setup_access() -> None:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
F
=================================== FAILURES ===================================
________________ test_ready_endpoint_unconfigured_setup_enabled ________________

test_client = <starlette.testclient.TestClient object at 0x78e9d9169260>

    def test_ready_endpoint_unconfigured_setup_enabled(test_client: TestClient):
        """Test readiness is 200 in setup mode so /setup stays reachable.
    
        An unconfigured pod with setup mode on must stay in the Service endpoints,
        otherwise the operator can never reach /setup to configure it.
        """
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.get.side_effect = lambda key, default=None: {
                "setup_enabled": True,
                "setup_allow_configured": False,
            }.get(key, default)
            response = test_client.get("/ready")
>       assert response.status_code == 200
E       assert 503 == 200
E        +  where 503 = <Response [503 Service Unavailable]>.status_code

tests/test_main.py:128: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 503 Service Unavailable\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:27:35.401287Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 503 Service Unavailable"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_unconfigured_setup_enabled - assert 503 == 200
 +  where 503 = <Response [503 Service Unavailable]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 216 passed, 3 deselected, 1 warning in 1.59s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2, definition_name: _require_setup_access
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -95,7 +95,7 @@
     Raises:
         HTTPException: If setup is disabled or automatically closed.
     """
-    if not _setup_requested():
+    if  _setup_requested():
         raise HTTPException(status_code=403, detail="Setup is disabled")
     if is_configured() and not _setup_allowed_when_configured():
         raise HTTPException(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..........................F
=================================== FAILURES ===================================
_____ TestSetupEndpointsConfigured.test_setup_auto_closes_when_configured ______

self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7f30950cb390>

    def test_setup_auto_closes_when_configured(self):
        """Test an initial-setup opt-in does not keep setup open after configuration."""
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(
                mock_settings,
                enabled=True,
                base_url="https://configured.example.test",
            )
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            response = TestClient(app, raise_server_exceptions=False).get("/setup")
    
        assert response.status_code == 403
>       assert "already configured" in response.json()["detail"].lower()
E       AssertionError: assert 'already configured' in 'setup is disabled'
E        +  where 'setup is disabled' = <built-in method lower of str object at 0x7f3094c76c30>()
E        +    where <built-in method lower of str object at 0x7f3094c76c30> = 'Setup is disabled'.lower

tests/test_setup_endpoints.py:99: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/setup \"HTTP/1.1 403 Forbidden\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:01:19.446096Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 403 Forbidden"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_auto_closes_when_configured - AssertionError: assert 'already configured' in 'setup is disabled'
 +  where 'setup is disabled' = <built-in method lower of str object at 0x7f3094c76c30>()
 +    where <built-in method lower of str object at 0x7f3094c76c30> = 'Setup is disabled'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 314 passed, 3 deselected, 1 warning in 2.27s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 3, definition_name: _require_setup_access
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -97,7 +97,7 @@
     """
     if not _setup_requested():
         raise HTTPException(status_code=403, detail="Setup is disabled")
-    if is_configured() and not _setup_allowed_when_configured():
+    if is_configured() and  _setup_allowed_when_configured():
         raise HTTPException(
             status_code=403,
             detail="Setup is closed because Stampbot is already configured",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..........................F
=================================== FAILURES ===================================
_____ TestSetupEndpointsConfigured.test_setup_auto_closes_when_configured ______

self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7d31c2fb7390>

    def test_setup_auto_closes_when_configured(self):
        """Test an initial-setup opt-in does not keep setup open after configuration."""
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(
                mock_settings,
                enabled=True,
                base_url="https://configured.example.test",
            )
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            response = TestClient(app, raise_server_exceptions=False).get("/setup")
    
>       assert response.status_code == 403
E       assert 200 == 403
E        +  where 200 = <Response [200 OK]>.status_code

tests/test_setup_endpoints.py:98: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/setup \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:42:33.124552Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_auto_closes_when_configured - assert 200 == 403
 +  where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 314 passed, 3 deselected, 1 warning in 2.29s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 4, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -145,7 +145,7 @@
 
     # Log setup mode status
     configured = is_configured()
-    if not configured:
+    if  configured:
         logger.warning("GitHub App credentials not configured.")
         if _setup_is_available(configured=False):
             logger.info("Setup is enabled; visit /setup to create your GitHub App")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 5, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -157,7 +157,7 @@
     if settings.metrics_enabled:
         metrics_host = str(settings.metrics_host)
         metrics_port = int(settings.metrics_port)
-        if not metrics_host.strip():
+        if  metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
         if not 1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x7e0f72188ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if  metrics_host.strip():
>               raise RuntimeError("metrics_host must not be empty")
E               RuntimeError: metrics_host must not be empty

stampbot/main.py:161: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:24:27.893329Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:24:27.893589Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:24:27.893329Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:24:27.893589Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_host must not be empty
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.19s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 6, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if  1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x74d14d284ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if  1 <= metrics_port <= 65535:
>               raise RuntimeError("metrics_port must be between 1 and 65535")
E               RuntimeError: metrics_port must be between 1 and 65535

stampbot/main.py:163: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:28:05.946546Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:28:05.946807Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:28:05.946546Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:28:05.946807Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must be between 1 and 65535
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.17s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 7, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -217,7 +217,7 @@
         match, child_scope = route.matches(request.scope)
         matched_route = child_scope.get("route", route)
         route_path = getattr(matched_route, "path", None)
-        if not isinstance(route_path, str):
+        if  isinstance(route_path, str):
             continue
         if match is Match.FULL:
             return route_path
........................................................................ [ 15%]
........................................................................ [ 30%]
..............................................F
=================================== FAILURES ===================================
____________________ test_dynamic_path_uses_route_template _____________________

routed_app = <fastapi.applications.FastAPI object at 0x7621350cb230>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='129884996022496'>, 'http_request_duration_seconds': ...='129884996025856'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='129884994204880'>, ...}

    def test_dynamic_path_uses_route_template(
        routed_app: FastAPI,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Path parameters are represented by their stable route template."""
        client = TestClient(routed_app)
    
        response = client.get("/widgets/user-controlled-123")
    
        assert response.status_code == 200
>       assert _endpoint_labels(http_metric_spies["http_requests_total"]) == ["/widgets/{widget_id}"]
E       AssertionError: assert ['unmatched'] == ['/widgets/{widget_id}']
E         
E         At index 0 diff: 'unmatched' != '/widgets/{widget_id}'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         -     '/widgets/{widget_id}',
E         +     'unmatched',
E           ]

tests/test_http_metrics.py:130: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:44:23.902573Z [info     ] HTTP Request: GET http://testserver/widgets/user-controlled-123 "HTTP/1.1 200 OK" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/widgets/user-controlled-123 "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_dynamic_path_uses_route_template - AssertionError: assert ['unmatched'] == ['/widgets/{widget_id}']
  
  At index 0 diff: 'unmatched' != '/widgets/{widget_id}'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     '/widgets/{widget_id}',
  +     'unmatched',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 190 passed, 3 deselected, 1 warning in 1.51s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 8, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -327,7 +327,7 @@
                     # entry is the original client (proxies append to the right).
                     client_ip = raw.split(",")[0].strip()
 
-            if not client_ip and scope.get("client"):
+            if  client_ip and scope.get("client"):
                 client_ip = scope["client"][0]
 
             if client_ip:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________

    def test_logging_middleware_respects_configured_header():
        """Test that logging_middleware uses the configured client_ip_header setting."""
        from unittest.mock import patch
    
        import structlog.contextvars
    
        from stampbot.main import app
    
        with patch("stampbot.main.settings") as mock_settings:
            mock_settings.get = lambda key, default=None: (
                "X-Real-IP" if key == "client_ip_header" else default
            )
    
            client = TestClient(app)
            captured: dict[str, str | None] = {}
    
            original_bind = structlog.contextvars.bind_contextvars
    
            def capturing_bind(**kw: object) -> None:
                captured.update({k: str(v) for k, v in kw.items()})
                original_bind(**kw)
    
            with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
                client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
    
>           assert captured.get("client_ip") == "198.51.100.7"
E           AssertionError: assert 'testclient' == '198.51.100.7'
E             
E             - 198.51.100.7
E             + testclient

tests/test_main.py:594: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/health \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:42:56.590286Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert 'testclient' == '198.51.100.7'
  
  - 198.51.100.7
  + testclient
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 239 passed, 3 deselected, 1 warning in 2.16s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 9, definition_name: root
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -347,7 +347,7 @@
         Redirect to /setup if unconfigured, otherwise JSON status response.
     """
     configured = is_configured()
-    if not configured and _setup_is_available(configured=False):
+    if  configured and _setup_is_available(configured=False):
         return RedirectResponse(url="/setup", status_code=307)
 
     return JSONResponse(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..............................F
=================================== FAILURES ===================================
_ TestSetupEndpointsUnconfigured.test_root_redirects_to_setup_when_unconfigured _

self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x75691fab34d0>

    def test_root_redirects_to_setup_when_unconfigured(self):
        """Test root redirects to /setup when not configured."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            client = TestClient(app, raise_server_exceptions=False)
            response = client.get("/", follow_redirects=False)
    
>           assert response.status_code == 307
E           assert 200 == 307
E            +  where 200 = <Response [200 OK]>.status_code

tests/test_setup_endpoints.py:187: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:32:54.936049Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_root_redirects_to_setup_when_unconfigured - assert 200 == 307
 +  where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 318 passed, 3 deselected, 1 warning in 2.30s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 10, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -415,7 +415,7 @@
     Returns:
         The GUID, or None when the header is missing or malformed.
     """
-    if not raw:
+    if  raw:
         return None
     if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x7e8081573240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
>       response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )

tests/test_main.py:356: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:528: in webhook
    delivery_id = _delivery_id_label(x_github_delivery)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

raw = None

    def _delivery_id_label(raw: str | None) -> str | None:
        """Return a GitHub delivery GUID suitable for logs and span attributes.
    
        GitHub sends a 36-character GUID. The check allows letters, digits, and
        hyphens up to 64 characters so a future format change still correlates,
        and drops anything else rather than storing it, since the value is only
        useful for matching GitHub's delivery log.
    
        Args:
            raw: Value of the ``X-GitHub-Delivery`` header, if present.
    
        Returns:
            The GUID, or None when the header is missing or malformed.
        """
        if  raw:
            return None
>       if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
           ^^^^^^^^
E       TypeError: object of type 'NoneType' has no len()

stampbot/main.py:420: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:27:41.141587Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - TypeError: object of type 'NoneType' has no len()
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.26s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 11, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -417,7 +417,7 @@
     """
     if not raw:
         return None
-    if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
+    if len(raw) > MAX_DELIVERY_ID_LENGTH or  DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
     return raw
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..........................F
=================================== FAILURES ===================================
__________________ test_webhook_passes_delivery_id_to_handler __________________

test_client = <starlette.testclient.TestClient object at 0x79041e6ab9b0>

    def test_webhook_passes_delivery_id_to_handler(test_client: TestClient):
        """Test the GitHub delivery GUID reaches the handler and the log context."""
        body = json.dumps({"zen": "test"}).encode()
        delivery = "72d3162e-cc78-11e3-81ab-4c9367dc0958"
        seen_context: dict[str, object] = {}
    
        async def fake_handle(event_type, payload, delivery_id=None):
            seen_context.update(structlog.contextvars.get_contextvars())
            return {"status": "ok", "message": event_type, "delivery": delivery_id}
    
        with patch("stampbot.main.webhook_handler.handle_event", side_effect=fake_handle):
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-GitHub-Delivery": delivery,
                    "X-Hub-Signature-256": _signed_ping(body),
                },
            )
    
        assert response.status_code == 200
>       assert response.json()["delivery"] == delivery
E       AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'

tests/test_main.py:692: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:02:15.327198Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_passes_delivery_id_to_handler - AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 242 passed, 3 deselected, 1 warning in 2.16s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 12, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -485,7 +485,7 @@
             413 if body too large, 503 if not configured, 500 on internal error.
     """
     # Check if app is configured
-    if not is_configured():
+    if  is_configured():
         raise HTTPException(
             status_code=503,
             detail="Stampbot not configured. Visit /setup to complete setup.",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x74fdf0eae9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 503 == 401
E        +  where 503 = <Response [503 Service Unavailable]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 503 Service Unavailable\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:31:29.436318Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 503 Service Unavailable"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 503 == 401
 +  where 503 = <Response [503 Service Unavailable]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.10s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 13, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -491,7 +491,7 @@
             detail="Stampbot not configured. Visit /setup to complete setup.",
         )
 
-    if not x_github_event:
+    if  x_github_event:
         errors_total.labels(error_type="missing_event").inc()
         raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x72cc496729c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 400 == 401
E        +  where 400 = <Response [400 Bad Request]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 400 Bad Request\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:43:32.087437Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 400 == 401
 +  where 400 = <Response [400 Bad Request]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.10s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 14, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -506,7 +506,7 @@
     body = await _read_body_within_limit(request)
 
     # Verify signature
-    if not webhook_handler.verify_signature(body, x_hub_signature_256):
+    if  webhook_handler.verify_signature(body, x_hub_signature_256):
         webhook_signature_validations_total.labels(result="invalid").inc()
         errors_total.labels(error_type="signature_invalid").inc()
         logger.warning("Invalid webhook signature")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x71ce1299a9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 200 == 401
E        +  where 200 = <Response [200 OK]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:18:15.181082Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:18:15.181789Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:18:15.181082Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 200 == 401
 +  where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.14s
operator: core/AddNot, occurrence: 0, definition_name: _setup_is_available
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -82,7 +82,7 @@
 
 def _setup_is_available(configured: bool | None = None) -> bool:
     """Return whether setup routes should be available for the current state."""
-    if not _setup_requested():
+    if not not _setup_requested():
         return False
 
     app_configured = is_configured() if configured is None else configured
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
F
=================================== FAILURES ===================================
________________ test_ready_endpoint_unconfigured_setup_enabled ________________

test_client = <starlette.testclient.TestClient object at 0x7de91bfa1260>

    def test_ready_endpoint_unconfigured_setup_enabled(test_client: TestClient):
        """Test readiness is 200 in setup mode so /setup stays reachable.
    
        An unconfigured pod with setup mode on must stay in the Service endpoints,
        otherwise the operator can never reach /setup to configure it.
        """
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.get.side_effect = lambda key, default=None: {
                "setup_enabled": True,
                "setup_allow_configured": False,
            }.get(key, default)
            response = test_client.get("/ready")
>       assert response.status_code == 200
E       assert 503 == 200
E        +  where 503 = <Response [503 Service Unavailable]>.status_code

tests/test_main.py:128: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 503 Service Unavailable\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:30:41.441895Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 503 Service Unavailable"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_unconfigured_setup_enabled - assert 503 == 200
 +  where 503 = <Response [503 Service Unavailable]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 216 passed, 3 deselected, 1 warning in 1.59s
operator: core/AddNot, occurrence: 1, definition_name: _setup_is_available
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -85,7 +85,7 @@
     if not _setup_requested():
         return False
 
-    app_configured = is_configured() if configured is None else configured
+    app_configured = is_configured() if not configured is None else configured
     return not app_configured or _setup_allowed_when_configured()
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/AddNot, occurrence: 2, definition_name: _require_setup_access
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -95,7 +95,7 @@
     Raises:
         HTTPException: If setup is disabled or automatically closed.
     """
-    if not _setup_requested():
+    if not not _setup_requested():
         raise HTTPException(status_code=403, detail="Setup is disabled")
     if is_configured() and not _setup_allowed_when_configured():
         raise HTTPException(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..........................F
=================================== FAILURES ===================================
_____ TestSetupEndpointsConfigured.test_setup_auto_closes_when_configured ______

self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7404165b7390>

    def test_setup_auto_closes_when_configured(self):
        """Test an initial-setup opt-in does not keep setup open after configuration."""
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(
                mock_settings,
                enabled=True,
                base_url="https://configured.example.test",
            )
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            response = TestClient(app, raise_server_exceptions=False).get("/setup")
    
        assert response.status_code == 403
>       assert "already configured" in response.json()["detail"].lower()
E       AssertionError: assert 'already configured' in 'setup is disabled'
E        +  where 'setup is disabled' = <built-in method lower of str object at 0x74041b0b28f0>()
E        +    where <built-in method lower of str object at 0x74041b0b28f0> = 'Setup is disabled'.lower

tests/test_setup_endpoints.py:99: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/setup \"HTTP/1.1 403 Forbidden\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:21:47.548687Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 403 Forbidden"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_auto_closes_when_configured - AssertionError: assert 'already configured' in 'setup is disabled'
 +  where 'setup is disabled' = <built-in method lower of str object at 0x74041b0b28f0>()
 +    where <built-in method lower of str object at 0x74041b0b28f0> = 'Setup is disabled'.lower
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 314 passed, 3 deselected, 1 warning in 2.28s
operator: core/AddNot, occurrence: 3, definition_name: _require_setup_access
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -97,7 +97,7 @@
     """
     if not _setup_requested():
         raise HTTPException(status_code=403, detail="Setup is disabled")
-    if is_configured() and not _setup_allowed_when_configured():
+    if not is_configured() and not _setup_allowed_when_configured():
         raise HTTPException(
             status_code=403,
             detail="Setup is closed because Stampbot is already configured",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..........................F
=================================== FAILURES ===================================
_____ TestSetupEndpointsConfigured.test_setup_auto_closes_when_configured ______

self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x773dab0cb390>

    def test_setup_auto_closes_when_configured(self):
        """Test an initial-setup opt-in does not keep setup open after configuration."""
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(
                mock_settings,
                enabled=True,
                base_url="https://configured.example.test",
            )
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            response = TestClient(app, raise_server_exceptions=False).get("/setup")
    
>       assert response.status_code == 403
E       assert 200 == 403
E        +  where 200 = <Response [200 OK]>.status_code

tests/test_setup_endpoints.py:98: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/setup \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:17:44.290845Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_auto_closes_when_configured - assert 200 == 403
 +  where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 314 passed, 3 deselected, 1 warning in 2.31s
operator: core/AddNot, occurrence: 4, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -145,7 +145,7 @@
 
     # Log setup mode status
     configured = is_configured()
-    if not configured:
+    if not not configured:
         logger.warning("GitHub App credentials not configured.")
         if _setup_is_available(configured=False):
             logger.info("Setup is enabled; visit /setup to create your GitHub App")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/AddNot, occurrence: 5, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -147,7 +147,7 @@
     configured = is_configured()
     if not configured:
         logger.warning("GitHub App credentials not configured.")
-        if _setup_is_available(configured=False):
+        if not _setup_is_available(configured=False):
             logger.info("Setup is enabled; visit /setup to create your GitHub App")
         else:
             logger.warning("Setup is disabled; enable it explicitly to provision the App")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/AddNot, occurrence: 6, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -154,7 +154,7 @@
     else:
         logger.info("GitHub App credentials configured successfully")
 
-    if settings.metrics_enabled:
+    if not settings.metrics_enabled:
         metrics_host = str(settings.metrics_host)
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
........................................................................ [ 15%]
........................................................................ [ 30%]
..................................................................F
=================================== FAILURES ===================================
______________________ test_lifespan_startup_unconfigured ______________________

    def test_lifespan_startup_unconfigured():
        """Test app lifespan startup when not configured (setup mode)."""
        from stampbot.main import app
    
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = False
    
>           with TestClient(app) as client:
                 ^^^^^^^^^^^^^^^

tests/test_main.py:46: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:166: in lifespan
    metrics_runtime = start_metrics_server(metrics_host, metrics_port)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/metrics.py:194: in start_metrics_server
    server, thread = start_http_server(port=port, addr=host, registry=registry)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/exposition.py:258: in start_wsgi_server
    TmpServer.address_family, addr = _get_best_family(addr, port)
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/exposition.py:187: in _get_best_family
    infos = socket.getaddrinfo(address, port, type=socket.SOCK_STREAM, flags=socket.AI_PASSIVE)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

host = "<MagicMock name='settings.metrics_host' id='136411836359952'>", port = 1
family = 0, type = <SocketKind.SOCK_STREAM: 1>, proto = 0
flags = <AddressInfo.AI_PASSIVE: 1>

    def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
        """Resolve host and port into list of address info entries.
    
        Translate the host/port argument into a sequence of 5-tuples that contain
        all the necessary arguments for creating a socket connected to that service.
        host is a domain name, a string representation of an IPv4/v6 address or
        None. port is a string service name such as 'http', a numeric port number or
        None. By passing None as the value of host and port, you can pass NULL to
        the underlying C API.
    
        The family, type and proto arguments can be optionally specified in order to
        narrow the list of addresses returned. Passing zero as a value for each of
        these arguments selects the full range of results.
        """
        # We override this function since we want to translate the numeric family
        # and socket type values to enum constants.
        addrlist = []
>       for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       socket.gaierror: [Errno -2] Name or service not known

/usr/lib/python3.14/socket.py:983: gaierror
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:28:55.992307Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials not configured.", "logger": "stampbot.main", "level": "warning", "timestamp": "2026-11-04T04:28:55.992494Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "warning"}
{"event": "Setup is enabled; visit /setup to create your GitHub App", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:28:55.992857Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:28:55.992307Z'}
WARNING  stampbot.main:main.py:149 {'event': 'GitHub App credentials not configured.', 'logger': 'stampbot.main', 'level': 'warning', 'timestamp': '2026-11-04T04:28:55.992494Z'}
INFO     stampbot.main:main.py:151 {'event': 'Setup is enabled; visit /setup to create your GitHub App', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:28:55.992857Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_unconfigured - socket.gaierror: [Errno -2] Name or service not known
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 210 passed, 3 deselected, 1 warning in 2.16s
operator: core/AddNot, occurrence: 7, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -157,7 +157,7 @@
     if settings.metrics_enabled:
         metrics_host = str(settings.metrics_host)
         metrics_port = int(settings.metrics_port)
-        if not metrics_host.strip():
+        if not not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
         if not 1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x76aabec7cad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not not metrics_host.strip():
>               raise RuntimeError("metrics_host must not be empty")
E               RuntimeError: metrics_host must not be empty

stampbot/main.py:161: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:25:00.315680Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:25:00.315920Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:25:00.315680Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:25:00.315920Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_host must not be empty
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.21s
operator: core/AddNot, occurrence: 8, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not not 1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x71c90b58cad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not not 1 <= metrics_port <= 65535:
>               raise RuntimeError("metrics_port must be between 1 and 65535")
E               RuntimeError: metrics_port must be between 1 and 65535

stampbot/main.py:163: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:29:29.042796Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:29:29.043060Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:29:29.042796Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:29:29.043060Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must be between 1 and 65535
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.18s
operator: core/AddNot, occurrence: 9, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -161,7 +161,7 @@
             raise RuntimeError("metrics_host must not be empty")
         if not 1 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
-        if metrics_port == int(settings.port):
+        if not metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
         metrics_runtime = start_metrics_server(metrics_host, metrics_port)
         logger.info(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....F
=================================== FAILURES ===================================
_______________ test_lifespan_starts_and_stops_metrics_listener ________________

    def test_lifespan_starts_and_stops_metrics_listener():
        """Test that metrics_enabled controls the dedicated listener lifecycle."""
        from stampbot.main import app
    
        server = MagicMock()
        thread = MagicMock()
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
            patch("stampbot.main.start_metrics_server", return_value=(server, thread)) as start,
            patch("stampbot.main.stop_metrics_server") as stop,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = "127.0.0.1"
            mock_settings.metrics_port = 9090
    
>           with TestClient(app):
                 ^^^^^^^^^^^^^^^

tests/test_main.py:196: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x7feb24c80ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 <= metrics_port <= 65535:
                raise RuntimeError("metrics_port must be between 1 and 65535")
            if not metrics_port == int(settings.port):
>               raise RuntimeError("metrics_port must differ from the public HTTP port")
E               RuntimeError: metrics_port must differ from the public HTTP port

stampbot/main.py:165: RuntimeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:49:34.594143Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:49:34.594427Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:49:34.594143Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:49:34.594427Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_starts_and_stops_metrics_listener - RuntimeError: metrics_port must differ from the public HTTP port
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 220 passed, 3 deselected, 1 warning in 2.23s
operator: core/AddNot, occurrence: 10, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -172,7 +172,7 @@
     try:
         yield
     finally:
-        if metrics_runtime is not None:
+        if not metrics_runtime is not None:
             stop_metrics_server(*metrics_runtime)
         # Shutdown
         logger.info("Shutting down stampbot")
........................................................................ [ 15%]
........................................................................ [ 30%]
.................................................................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________

    def test_lifespan_startup_shutdown_configured():
        """Test app lifespan startup and shutdown when configured."""
        from stampbot.main import app
    
        # Use TestClient as context manager to trigger lifespan events
>       with TestClient(app) as client:
             ^^^^^^^^^^^^^^^

tests/test_main.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:584: in __exit__
    self.exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:482: in _exit_wrapper
    callback(*args, **kwds)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:576: in wait_shutdown
    portal.call(self.wait_shutdown)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:624: in wait_shutdown
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:614: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:221: in __aexit__
    await anext(self.gen)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

app = <fastapi.applications.FastAPI object at 0x742f83984ad0>

    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncIterator[None]:
        """Application lifespan manager for startup and shutdown events.
    
        Args:
            app: FastAPI application instance.
    
        Yields:
            None after startup, resumes for shutdown.
        """
        metrics_runtime: tuple[Any, Any] | None = None
    
        # Startup
        logger.info(
            f"Starting {settings.app_name} on {settings.host}:{settings.port}",
            extra={
                "host": settings.host,
                "port": settings.port,
                "log_level": settings.log_level,
            },
        )
    
        # Log setup mode status
        configured = is_configured()
        if not configured:
            logger.warning("GitHub App credentials not configured.")
            if _setup_is_available(configured=False):
                logger.info("Setup is enabled; visit /setup to create your GitHub App")
            else:
                logger.warning("Setup is disabled; enable it explicitly to provision the App")
        else:
            logger.info("GitHub App credentials configured successfully")
    
        if settings.metrics_enabled:
            metrics_host = str(settings.metrics_host)
            metrics_port = int(settings.metrics_port)
            if not metrics_host.strip():
                raise RuntimeError("metrics_host must not be empty")
            if not 1 <= metrics_port <= 65535:
                raise RuntimeError("metrics_port must be between 1 and 65535")
            if metrics_port == int(settings.port):
                raise RuntimeError("metrics_port must differ from the public HTTP port")
            metrics_runtime = start_metrics_server(metrics_host, metrics_port)
            logger.info(
                "Prometheus metrics listener started",
                extra={"host": metrics_host, "port": metrics_port},
            )
    
        try:
            yield
        finally:
            if not metrics_runtime is not None:
>               stop_metrics_server(*metrics_runtime)
E               TypeError: stampbot.metrics.stop_metrics_server() argument after * must be an iterable, not NoneType

stampbot/main.py:176: TypeError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:32:10.843114Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:32:10.843268Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "HTTP Request: GET http://testserver/health \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:32:10.844758Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:32:10.843114Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:32:10.843268Z'}
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - TypeError: stampbot.metrics.stop_metrics_server() argument after * must be an iterable, not NoneType
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 209 passed, 3 deselected, 1 warning in 1.64s
operator: core/AddNot, occurrence: 11, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -209,7 +209,7 @@
     """
     current_route = request.scope.get("route")
     current_path = getattr(current_route, "path", None)
-    if isinstance(current_path, str):
+    if not isinstance(current_path, str):
         return current_path
 
     partial_match: str | None = None
........................................................................ [ 15%]
........................................................................ [ 30%]
..........................................F
=================================== FAILURES ===================================
_________________ test_existing_scope_route_uses_its_template __________________

    def test_existing_scope_route_uses_its_template() -> None:
        """A route resolved by an outer router is used without rematching."""
        request = Request(
            {
                "type": "http",
                "route": SimpleNamespace(path="/mounted/{resource_id}"),
            }
        )
    
>       assert _metric_endpoint_label(request) == "/mounted/{resource_id}"
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:69: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/main.py:216: in _metric_endpoint_label
    for route in request.app.routes:
                 ^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x7b59c9f15be0>

    @property
    def app(self) -> Any:
>       return self.scope["app"]
               ^^^^^^^^^^^^^^^^^
E       KeyError: 'app'

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:108: KeyError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_existing_scope_route_uses_its_template - KeyError: 'app'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 186 passed, 3 deselected, 1 warning in 1.47s
operator: core/AddNot, occurrence: 12, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -217,7 +217,7 @@
         match, child_scope = route.matches(request.scope)
         matched_route = child_scope.get("route", route)
         route_path = getattr(matched_route, "path", None)
-        if not isinstance(route_path, str):
+        if not not isinstance(route_path, str):
             continue
         if match is Match.FULL:
             return route_path
........................................................................ [ 15%]
........................................................................ [ 30%]
..............................................F
=================================== FAILURES ===================================
____________________ test_dynamic_path_uses_route_template _____________________

routed_app = <fastapi.applications.FastAPI object at 0x7ab190ae7230>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='134903055151328'>, 'http_request_duration_seconds': ...='134903055154688'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='134903053333712'>, ...}

    def test_dynamic_path_uses_route_template(
        routed_app: FastAPI,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Path parameters are represented by their stable route template."""
        client = TestClient(routed_app)
    
        response = client.get("/widgets/user-controlled-123")
    
        assert response.status_code == 200
>       assert _endpoint_labels(http_metric_spies["http_requests_total"]) == ["/widgets/{widget_id}"]
E       AssertionError: assert ['unmatched'] == ['/widgets/{widget_id}']
E         
E         At index 0 diff: 'unmatched' != '/widgets/{widget_id}'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         -     '/widgets/{widget_id}',
E         +     'unmatched',
E           ]

tests/test_http_metrics.py:130: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:32:36.185046Z [info     ] HTTP Request: GET http://testserver/widgets/user-controlled-123 "HTTP/1.1 200 OK" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/widgets/user-controlled-123 "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_dynamic_path_uses_route_template - AssertionError: assert ['unmatched'] == ['/widgets/{widget_id}']
  
  At index 0 diff: 'unmatched' != '/widgets/{widget_id}'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     '/widgets/{widget_id}',
  +     'unmatched',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 190 passed, 3 deselected, 1 warning in 1.50s
operator: core/AddNot, occurrence: 13, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -219,7 +219,7 @@
         route_path = getattr(matched_route, "path", None)
         if not isinstance(route_path, str):
             continue
-        if match is Match.FULL:
+        if not match is Match.FULL:
             return route_path
         if match is Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x70b11b0f9fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='123905965533440'>, 'http_request_duration_seconds': ...='123905965534112'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='123905965534448'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
            response = test_client.post(path, content=b"request body")
            assert response.status_code == 404
    
>       assert _endpoint_labels(http_metric_spies["http_requests_total"]) == [
            UNMATCHED_ENDPOINT,
            UNMATCHED_ENDPOINT,
        ]
E       AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
E         
E         At index 0 diff: '/openapi.json' != 'unmatched'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         -     'unmatched',
E         -     'unmatched',
E         +     '/openapi.json',
E         +     '/openapi.json',
E           ]

tests/test_http_metrics.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:12:05.986297Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found" [httpx]
2026-11-04T03:12:05.988830Z [info     ] HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found"
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
  
  At index 0 diff: '/openapi.json' != 'unmatched'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     'unmatched',
  -     'unmatched',
  +     '/openapi.json',
  +     '/openapi.json',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.49s
operator: core/AddNot, occurrence: 14, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -221,7 +221,7 @@
             continue
         if match is Match.FULL:
             return route_path
-        if match is Match.PARTIAL and partial_match is None:
+        if not match is Match.PARTIAL and partial_match is None:
             # A path match with the wrong method will become a 405 response.
             partial_match = route_path
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x7257f0bf5fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='125722026797312'>, 'http_request_duration_seconds': ...='125722026797984'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='125722026798320'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
            response = test_client.post(path, content=b"request body")
            assert response.status_code == 404
    
>       assert _endpoint_labels(http_metric_spies["http_requests_total"]) == [
            UNMATCHED_ENDPOINT,
            UNMATCHED_ENDPOINT,
        ]
E       AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
E         
E         At index 0 diff: '/openapi.json' != 'unmatched'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         -     'unmatched',
E         -     'unmatched',
E         +     '/openapi.json',
E         +     '/openapi.json',
E           ]

tests/test_http_metrics.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:29:22.941640Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found" [httpx]
2026-11-04T04:29:22.944311Z [info     ] HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found"
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
  
  At index 0 diff: '/openapi.json' != 'unmatched'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     'unmatched',
  -     'unmatched',
  +     '/openapi.json',
  +     '/openapi.json',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.49s
operator: core/AddNot, occurrence: 15, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -266,7 +266,7 @@
 
         # Track request size (from Content-Length header if available)
         content_length = request.headers.get("content-length")
-        if content_length:
+        if not content_length:
             http_request_size_bytes.labels(
                 method=method,
                 endpoint=endpoint,
........................................................................ [ 15%]
........................................................................ [ 30%]
.............................................F
=================================== FAILURES ===================================
___________ test_exported_metrics_never_contain_an_unknown_raw_path ____________

test_client = <starlette.testclient.TestClient object at 0x70b9b3b5fed0>

    def test_exported_metrics_never_contain_an_unknown_raw_path(test_client: TestClient) -> None:
        """The metrics registry exports only the bounded fallback for a 404."""
        raw_path = "/unknown/security-regression-canary-8f3810"
    
>       assert test_client.get(raw_path).status_code == 404
               ^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:113: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x70b9b38782d0>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x70b9b383d4e0>

    @app.middleware("http")
    async def metrics_middleware(request: Request, call_next: Any) -> Response:
        """Middleware to track HTTP metrics.
    
        Args:
            request: Incoming HTTP request.
            call_next: Next middleware or route handler.
    
        Returns:
            HTTP response from downstream handler.
        """
        method = request.method
        endpoint = _metric_endpoint_label(request)
    
        # Track in-progress requests
        http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
    
        start_time = time.time()
    
        try:
            response = await call_next(request)
    
            duration = time.time() - start_time
    
            # Track request metrics
            http_requests_total.labels(
                method=method,
                endpoint=endpoint,
                status=response.status_code,
            ).inc()
    
            http_request_duration_seconds.labels(
                method=method,
                endpoint=endpoint,
            ).observe(duration)
    
            # Track request size (from Content-Length header if available)
            content_length = request.headers.get("content-length")
            if not content_length:
                http_request_size_bytes.labels(
                    method=method,
                    endpoint=endpoint,
>               ).observe(int(content_length))
                          ^^^^^^^^^^^^^^^^^^^
E               TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'

stampbot/main.py:273: TypeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:07:57.517282Z [info     ] HTTP Request: GET http://testserver/unknown/security-regression-canary-8f3810 "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/unknown/security-regression-canary-8f3810 "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_exported_metrics_never_contain_an_unknown_raw_path - TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 189 passed, 3 deselected, 1 warning in 1.56s
operator: core/AddNot, occurrence: 16, definition_name: metrics_middleware
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -274,7 +274,7 @@
 
         # Track response size
         response_size = response.headers.get("content-length")
-        if response_size:
+        if not response_size:
             http_response_size_bytes.labels(
                 method=method,
                 endpoint=endpoint,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
....................F
=================================== FAILURES ===================================
_____________________ test_response_without_content_length _____________________

    def test_response_without_content_length():
        """Test that responses without Content-Length header are handled correctly.
    
        This tests the branch where response_size is None (line 142->148).
        StreamingResponse doesn't include Content-Length header.
        """
        from collections.abc import AsyncIterator
    
        from fastapi.responses import StreamingResponse
    
        from stampbot.main import app
    
        # Track if our streaming endpoint was added
        streaming_route_path = "/_test_streaming_no_cl"
    
        async def generate() -> AsyncIterator[bytes]:
            yield b"chunk1"
            yield b"chunk2"
    
        # Add a temporary streaming endpoint
        @app.get(streaming_route_path)
        async def streaming_no_content_length() -> StreamingResponse:
            return StreamingResponse(generate(), media_type="text/plain")
    
        client = TestClient(app)
>       response = client.get(streaming_route_path)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_main.py:545: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.middleware.base._CachedRequest object at 0x71ca8f6ed090>
call_next = <function BaseHTTPMiddleware.__call__.<locals>.call_next at 0x71ca8f6b5640>

    @app.middleware("http")
    async def metrics_middleware(request: Request, call_next: Any) -> Response:
        """Middleware to track HTTP metrics.
    
        Args:
            request: Incoming HTTP request.
            call_next: Next middleware or route handler.
    
        Returns:
            HTTP response from downstream handler.
        """
        method = request.method
        endpoint = _metric_endpoint_label(request)
    
        # Track in-progress requests
        http_requests_in_progress.labels(method=method, endpoint=endpoint).inc()
    
        start_time = time.time()
    
        try:
            response = await call_next(request)
    
            duration = time.time() - start_time
    
            # Track request metrics
            http_requests_total.labels(
                method=method,
                endpoint=endpoint,
                status=response.status_code,
            ).inc()
    
            http_request_duration_seconds.labels(
                method=method,
                endpoint=endpoint,
            ).observe(duration)
    
            # Track request size (from Content-Length header if available)
            content_length = request.headers.get("content-length")
            if content_length:
                http_request_size_bytes.labels(
                    method=method,
                    endpoint=endpoint,
                ).observe(int(content_length))
    
            # Track response size
            response_size = response.headers.get("content-length")
            if not response_size:
                http_response_size_bytes.labels(
                    method=method,
                    endpoint=endpoint,
>               ).observe(int(response_size))
                          ^^^^^^^^^^^^^^^^^^
E               TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'

stampbot/main.py:281: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/_test_streaming_no_cl \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:25:18.661738Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/_test_streaming_no_cl "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_response_without_content_length - TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 236 passed, 3 deselected, 1 warning in 2.21s
operator: core/AddNot, occurrence: 17, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -314,7 +314,7 @@
             receive: ASGI receive callable.
             send: ASGI send callable.
         """
-        if scope["type"] == "http":
+        if not scope["type"] == "http":
             structlog.contextvars.clear_contextvars()
 
             client_ip: str | None = None
........................................................................ [ 15%]
........................................................................ [ 30%]
.................................................................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________

    def test_lifespan_startup_shutdown_configured():
        """Test app lifespan startup and shutdown when configured."""
        from stampbot.main import app
    
        # Use TestClient as context manager to trigger lifespan events
>       with TestClient(app) as client:
             ^^^^^^^^^^^^^^^

tests/test_main.py:26: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:602: in wait_startup
    message = await receive()
              ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:324: in __call__
    raw = Request(scope).headers.get(client_ip_header)
          ^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:219: in __init__
    super().__init__(scope)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <starlette.requests.Request object at 0x7a15a055bdd0>
scope = {'type': 'lifespan', 'state': {}, 'app': <fastapi.applications.FastAPI object at 0x7a15a089cad0>}
receive = None

    def __init__(self, scope: Scope, receive: Receive | None = None) -> None:
>       assert scope["type"] in ("http", "websocket")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AssertionError

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/requests.py:88: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - AssertionError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 209 passed, 3 deselected, 1 warning in 1.58s
operator: core/AddNot, occurrence: 18, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -320,7 +320,7 @@
             client_ip: str | None = None
             client_ip_header: str = settings.get("client_ip_header", "X-Forwarded-For")
 
-            if client_ip_header:
+            if not client_ip_header:
                 raw = Request(scope).headers.get(client_ip_header)
                 if raw:
                     # X-Forwarded-For may be a comma-separated list; the leftmost
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________

    def test_logging_middleware_respects_configured_header():
        """Test that logging_middleware uses the configured client_ip_header setting."""
        from unittest.mock import patch
    
        import structlog.contextvars
    
        from stampbot.main import app
    
        with patch("stampbot.main.settings") as mock_settings:
            mock_settings.get = lambda key, default=None: (
                "X-Real-IP" if key == "client_ip_header" else default
            )
    
            client = TestClient(app)
            captured: dict[str, str | None] = {}
    
            original_bind = structlog.contextvars.bind_contextvars
    
            def capturing_bind(**kw: object) -> None:
                captured.update({k: str(v) for k, v in kw.items()})
                original_bind(**kw)
    
            with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
                client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
    
>           assert captured.get("client_ip") == "198.51.100.7"
E           AssertionError: assert 'testclient' == '198.51.100.7'
E             
E             - 198.51.100.7
E             + testclient

tests/test_main.py:594: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/health \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:30:58.870698Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert 'testclient' == '198.51.100.7'
  
  - 198.51.100.7
  + testclient
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 239 passed, 3 deselected, 1 warning in 2.19s
operator: core/AddNot, occurrence: 19, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -322,7 +322,7 @@
 
             if client_ip_header:
                 raw = Request(scope).headers.get(client_ip_header)
-                if raw:
+                if not raw:
                     # X-Forwarded-For may be a comma-separated list; the leftmost
                     # entry is the original client (proxies append to the right).
                     client_ip = raw.split(",")[0].strip()
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x702d9ea09fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='123341237168384'>, 'http_request_duration_seconds': ...='123341237169056'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='123341237169392'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
>           response = test_client.post(path, content=b"request body")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_http_metrics.py:98: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.main.LoggingMiddleware object at 0x702d9ea4c590>
scope = {'type': 'http', 'http_version': '1.1', 'method': 'POST', 'path': '/not-a-route/attacker-value-one', ...}
receive = <function _TestClientTransport.handle_request.<locals>.receive at 0x702d9ec8b7f0>
send = <function ServerErrorMiddleware.__call__.<locals>._send at 0x702d9ea45170>

    async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
        """Process an ASGI event, binding client IP to the structlog context.
    
        For HTTP scopes the middleware clears any inherited structlog context,
        extracts the real client IP from the configured forwarding header (or
        the direct connection address as a fallback), and binds it so that
        every log record emitted during the request includes ``client_ip``.
    
        Non-HTTP scopes (lifespan, WebSocket) are passed through unchanged.
    
        Args:
            scope: ASGI connection scope.
            receive: ASGI receive callable.
            send: ASGI send callable.
        """
        if scope["type"] == "http":
            structlog.contextvars.clear_contextvars()
    
            client_ip: str | None = None
            client_ip_header: str = settings.get("client_ip_header", "X-Forwarded-For")
    
            if client_ip_header:
                raw = Request(scope).headers.get(client_ip_header)
                if not raw:
                    # X-Forwarded-For may be a comma-separated list; the leftmost
                    # entry is the original client (proxies append to the right).
>                   client_ip = raw.split(",")[0].strip()
                                ^^^^^^^^^
E                   AttributeError: 'NoneType' object has no attribute 'split'

stampbot/main.py:328: AttributeError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:32:40.148913Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - AttributeError: 'NoneType' object has no attribute 'split'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.56s
operator: core/AddNot, occurrence: 20, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -327,7 +327,7 @@
                     # entry is the original client (proxies append to the right).
                     client_ip = raw.split(",")[0].strip()
 
-            if not client_ip and scope.get("client"):
+            if not not client_ip and scope.get("client"):
                 client_ip = scope["client"][0]
 
             if client_ip:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________

    def test_logging_middleware_respects_configured_header():
        """Test that logging_middleware uses the configured client_ip_header setting."""
        from unittest.mock import patch
    
        import structlog.contextvars
    
        from stampbot.main import app
    
        with patch("stampbot.main.settings") as mock_settings:
            mock_settings.get = lambda key, default=None: (
                "X-Real-IP" if key == "client_ip_header" else default
            )
    
            client = TestClient(app)
            captured: dict[str, str | None] = {}
    
            original_bind = structlog.contextvars.bind_contextvars
    
            def capturing_bind(**kw: object) -> None:
                captured.update({k: str(v) for k, v in kw.items()})
                original_bind(**kw)
    
            with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
                client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
    
>           assert captured.get("client_ip") == "198.51.100.7"
E           AssertionError: assert 'testclient' == '198.51.100.7'
E             
E             - 198.51.100.7
E             + testclient

tests/test_main.py:594: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/health \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:00:38.425505Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert 'testclient' == '198.51.100.7'
  
  - 198.51.100.7
  + testclient
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 239 passed, 3 deselected, 1 warning in 2.16s
operator: core/AddNot, occurrence: 21, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -330,7 +330,7 @@
             if not client_ip and scope.get("client"):
                 client_ip = scope["client"][0]
 
-            if client_ip:
+            if not client_ip:
                 structlog.contextvars.bind_contextvars(client_ip=client_ip)
 
         await self.app(scope, receive, send)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________

    def test_logging_middleware_respects_configured_header():
        """Test that logging_middleware uses the configured client_ip_header setting."""
        from unittest.mock import patch
    
        import structlog.contextvars
    
        from stampbot.main import app
    
        with patch("stampbot.main.settings") as mock_settings:
            mock_settings.get = lambda key, default=None: (
                "X-Real-IP" if key == "client_ip_header" else default
            )
    
            client = TestClient(app)
            captured: dict[str, str | None] = {}
    
            original_bind = structlog.contextvars.bind_contextvars
    
            def capturing_bind(**kw: object) -> None:
                captured.update({k: str(v) for k, v in kw.items()})
                original_bind(**kw)
    
            with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
                client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
    
>           assert captured.get("client_ip") == "198.51.100.7"
E           AssertionError: assert None == '198.51.100.7'
E            +  where None = <built-in method get of dict object at 0x7b1e87ef78c0>('client_ip')
E            +    where <built-in method get of dict object at 0x7b1e87ef78c0> = {}.get

tests/test_main.py:594: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/health \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:54:47.875521Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert None == '198.51.100.7'
 +  where None = <built-in method get of dict object at 0x7b1e87ef78c0>('client_ip')
 +    where <built-in method get of dict object at 0x7b1e87ef78c0> = {}.get
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 239 passed, 3 deselected, 1 warning in 2.18s
operator: core/AddNot, occurrence: 22, definition_name: root
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -347,7 +347,7 @@
         Redirect to /setup if unconfigured, otherwise JSON status response.
     """
     configured = is_configured()
-    if not configured and _setup_is_available(configured=False):
+    if not not configured and _setup_is_available(configured=False):
         return RedirectResponse(url="/setup", status_code=307)
 
     return JSONResponse(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..............................F
=================================== FAILURES ===================================
_ TestSetupEndpointsUnconfigured.test_root_redirects_to_setup_when_unconfigured _

self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7e7084e9b4d0>

    def test_root_redirects_to_setup_when_unconfigured(self):
        """Test root redirects to /setup when not configured."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            client = TestClient(app, raise_server_exceptions=False)
            response = client.get("/", follow_redirects=False)
    
>           assert response.status_code == 307
E           assert 200 == 307
E            +  where 200 = <Response [200 OK]>.status_code

tests/test_setup_endpoints.py:187: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:25:18.305282Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_root_redirects_to_setup_when_unconfigured - assert 200 == 307
 +  where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 318 passed, 3 deselected, 1 warning in 2.32s
operator: core/AddNot, occurrence: 23, definition_name: ready
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -396,7 +396,7 @@
     checks = {"configured": configured, "setup_enabled": setup_enabled}
     is_ready = configured or setup_enabled
     return JSONResponse(
-        status_code=200 if is_ready else 503,
+        status_code=200 if not is_ready else 503,
         content={"status": "ready" if is_ready else "not ready", "checks": checks},
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.......................................................................F [ 45%]

=================================== FAILURES ===================================
________________________ test_ready_endpoint_configured ________________________

test_client = <starlette.testclient.TestClient object at 0x75766747d370>

    def test_ready_endpoint_configured(test_client: TestClient):
        """Test readiness endpoint returns 200 when the app is configured."""
        with patch("stampbot.main.is_configured", return_value=True):
            response = test_client.get("/ready")
>       assert response.status_code == 200
E       assert 503 == 200
E        +  where 503 = <Response [503 Service Unavailable]>.status_code

tests/test_main.py:107: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 503 Service Unavailable\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:14:06.793296Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 503 Service Unavailable"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_configured - assert 503 == 200
 +  where 503 = <Response [503 Service Unavailable]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 215 passed, 3 deselected, 1 warning in 1.55s
operator: core/AddNot, occurrence: 24, definition_name: ready
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -397,7 +397,7 @@
     is_ready = configured or setup_enabled
     return JSONResponse(
         status_code=200 if is_ready else 503,
-        content={"status": "ready" if is_ready else "not ready", "checks": checks},
+        content={"status": "ready" if not is_ready else "not ready", "checks": checks},
     )
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.......................................................................F [ 45%]

=================================== FAILURES ===================================
________________________ test_ready_endpoint_configured ________________________

test_client = <starlette.testclient.TestClient object at 0x768a7026d370>

    def test_ready_endpoint_configured(test_client: TestClient):
        """Test readiness endpoint returns 200 when the app is configured."""
        with patch("stampbot.main.is_configured", return_value=True):
            response = test_client.get("/ready")
        assert response.status_code == 200
        data = response.json()
>       assert data["status"] == "ready"
E       AssertionError: assert 'not ready' == 'ready'
E         
E         - ready
E         + not ready

tests/test_main.py:109: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:08:28.067556Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_configured - AssertionError: assert 'not ready' == 'ready'
  
  - ready
  + not ready
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 215 passed, 3 deselected, 1 warning in 1.57s
operator: core/AddNot, occurrence: 25, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -415,7 +415,7 @@
     Returns:
         The GUID, or None when the header is missing or malformed.
     """
-    if not raw:
+    if not not raw:
         return None
     if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............F
=================================== FAILURES ===================================
___________________________ test_webhook_valid_ping ____________________________

test_client = <starlette.testclient.TestClient object at 0x7d9b8c08f240>

    def test_webhook_valid_ping(test_client: TestClient):
        """Test webhook accepts valid ping event."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "Design for failure."}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
>       response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )

tests/test_main.py:356: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:528: in webhook
    delivery_id = _delivery_id_label(x_github_delivery)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

raw = None

    def _delivery_id_label(raw: str | None) -> str | None:
        """Return a GitHub delivery GUID suitable for logs and span attributes.
    
        GitHub sends a 36-character GUID. The check allows letters, digits, and
        hyphens up to 64 characters so a future format change still correlates,
        and drops anything else rather than storing it, since the value is only
        useful for matching GitHub's delivery log.
    
        Args:
            raw: Value of the ``X-GitHub-Delivery`` header, if present.
    
        Returns:
            The GUID, or None when the header is missing or malformed.
        """
        if not not raw:
            return None
>       if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
           ^^^^^^^^
E       TypeError: object of type 'NoneType' has no len()

stampbot/main.py:420: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:29:09.507794Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_valid_ping - TypeError: object of type 'NoneType' has no len()
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 230 passed, 3 deselected, 1 warning in 2.24s
operator: core/AddNot, occurrence: 26, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -417,7 +417,7 @@
     """
     if not raw:
         return None
-    if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
+    if not len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
     return raw
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..........................F
=================================== FAILURES ===================================
__________________ test_webhook_passes_delivery_id_to_handler __________________

test_client = <starlette.testclient.TestClient object at 0x7de5bfc4b9b0>

    def test_webhook_passes_delivery_id_to_handler(test_client: TestClient):
        """Test the GitHub delivery GUID reaches the handler and the log context."""
        body = json.dumps({"zen": "test"}).encode()
        delivery = "72d3162e-cc78-11e3-81ab-4c9367dc0958"
        seen_context: dict[str, object] = {}
    
        async def fake_handle(event_type, payload, delivery_id=None):
            seen_context.update(structlog.contextvars.get_contextvars())
            return {"status": "ok", "message": event_type, "delivery": delivery_id}
    
        with patch("stampbot.main.webhook_handler.handle_event", side_effect=fake_handle):
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-GitHub-Delivery": delivery,
                    "X-Hub-Signature-256": _signed_ping(body),
                },
            )
    
        assert response.status_code == 200
>       assert response.json()["delivery"] == delivery
E       AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'

tests/test_main.py:692: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:30:31.624923Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_passes_delivery_id_to_handler - AssertionError: assert None == '72d3162e-cc78-11e3-81ab-4c9367dc0958'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 242 passed, 3 deselected, 1 warning in 2.20s
operator: core/AddNot, occurrence: 27, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -453,7 +453,7 @@
     """
     body = bytearray()
     async for chunk in request.stream():
-        if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
+        if not len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x75de0249e9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:32:29.953432Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.11s
operator: core/AddNot, occurrence: 28, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -485,7 +485,7 @@
             413 if body too large, 503 if not configured, 500 on internal error.
     """
     # Check if app is configured
-    if not is_configured():
+    if not not is_configured():
         raise HTTPException(
             status_code=503,
             detail="Stampbot not configured. Visit /setup to complete setup.",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7755de7ba9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 503 == 401
E        +  where 503 = <Response [503 Service Unavailable]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 503 Service Unavailable\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:02:30.454102Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 503 Service Unavailable"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 503 == 401
 +  where 503 = <Response [503 Service Unavailable]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.08s
operator: core/AddNot, occurrence: 29, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -491,7 +491,7 @@
             detail="Stampbot not configured. Visit /setup to complete setup.",
         )
 
-    if not x_github_event:
+    if not not x_github_event:
         errors_total.labels(error_type="missing_event").inc()
         raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7d05d239e9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 400 == 401
E        +  where 400 = <Response [400 Bad Request]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 400 Bad Request\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:24:12.375126Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 400 == 401
 +  where 400 = <Response [400 Bad Request]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.11s
operator: core/AddNot, occurrence: 30, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -497,7 +497,7 @@
 
     # Refuse a declared-oversize body without reading any of it.
     content_length = request.headers.get("content-length")
-    if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+    if not content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
         errors_total.labels(error_type="payload_too_large").inc()
         raise HTTPException(status_code=413, detail="Request body too large")
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________

test_client = <starlette.testclient.TestClient object at 0x7120b81979b0>

    def test_webhook_content_length_too_large(test_client: TestClient):
        """A declared-oversize body is refused without reading it."""
        response = test_client.post(
            "/webhook",
            content=b"x",  # Small actual body
            headers={
                "Content-Type": "application/json",
                "Content-Length": str(MAX_WEBHOOK_BODY_SIZE + 1),
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=fake",
            },
        )
>       assert response.status_code == 413
E       assert 401 == 413
E        +  where 401 = <Response [401 Unauthorized]>.status_code

tests/test_main.py:383: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Invalid webhook signature", "client_ip": "testclient", "logger": "stampbot.main", "level": "warning", "timestamp": "2026-11-04T04:43:12.011918Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 401 Unauthorized\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:43:12.012445Z"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.main:main.py:512 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'warning', 'timestamp': '2026-11-04T04:43:12.011918Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 401 == 413
 +  where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 231 passed, 3 deselected, 1 warning in 2.11s
operator: core/AddNot, occurrence: 31, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -506,7 +506,7 @@
     body = await _read_body_within_limit(request)
 
     # Verify signature
-    if not webhook_handler.verify_signature(body, x_hub_signature_256):
+    if not not webhook_handler.verify_signature(body, x_hub_signature_256):
         webhook_signature_validations_total.labels(result="invalid").inc()
         errors_total.labels(error_type="signature_invalid").inc()
         logger.warning("Invalid webhook signature")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7f84f776e9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 200 == 401
E        +  where 200 = <Response [200 OK]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "ping", "action": ""}, "event": "Received ping event", "client_ip": "testclient", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T04:32:05.625461Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:32:05.626070Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'ping', 'action': ''}, 'event': 'Received ping event', 'client_ip': 'testclient', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T04:32:05.625461Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 200 == 401
 +  where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.11s
operator: core/AddNot, occurrence: 32, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -526,7 +526,7 @@
     # The header is only trusted once the HMAC check has passed. Bound length
     # keeps a malformed value out of log storage.
     delivery_id = _delivery_id_label(x_github_delivery)
-    if delivery_id:
+    if not delivery_id:
         structlog.contextvars.bind_contextvars(delivery_id=delivery_id)
 
     # Handle event with timing
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..........................F
=================================== FAILURES ===================================
__________________ test_webhook_passes_delivery_id_to_handler __________________

test_client = <starlette.testclient.TestClient object at 0x7b2b2bd779b0>

    def test_webhook_passes_delivery_id_to_handler(test_client: TestClient):
        """Test the GitHub delivery GUID reaches the handler and the log context."""
        body = json.dumps({"zen": "test"}).encode()
        delivery = "72d3162e-cc78-11e3-81ab-4c9367dc0958"
        seen_context: dict[str, object] = {}
    
        async def fake_handle(event_type, payload, delivery_id=None):
            seen_context.update(structlog.contextvars.get_contextvars())
            return {"status": "ok", "message": event_type, "delivery": delivery_id}
    
        with patch("stampbot.main.webhook_handler.handle_event", side_effect=fake_handle):
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-GitHub-Delivery": delivery,
                    "X-Hub-Signature-256": _signed_ping(body),
                },
            )
    
        assert response.status_code == 200
        assert response.json()["delivery"] == delivery
>       assert seen_context["delivery_id"] == delivery
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       KeyError: 'delivery_id'

tests/test_main.py:693: KeyError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:17:41.869509Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_passes_delivery_id_to_handler - KeyError: 'delivery_id'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 242 passed, 3 deselected, 1 warning in 2.18s
operator: core/ReplaceTrueWithFalse, occurrence: 0, definition_name: setup_page
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -575,7 +575,7 @@
     manifest = create_manifest(redirect_url, webhook_url=webhook_url)
     # HTML-escape the JSON for safe embedding in the form
     manifest_json = html.escape(json.dumps(manifest))
-    webhook_url_escaped = html.escape(webhook_url, quote=True)
+    webhook_url_escaped = html.escape(webhook_url, quote=False)
 
     html_content = f"""
     <!DOCTYPE html>
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.31s
operator: core/ReplaceTrueWithFalse, occurrence: 1, definition_name: setup_callback
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -661,7 +661,7 @@
     # 3. Users can immediately rotate the webhook secret if concerned
     # 4. The alternative (file download) adds friction without meaningful security gain
     #    since the credentials must be transmitted to the browser regardless
-    private_key_escaped = html.escape(str(credentials["pem"]).replace("\n", "\\n"), quote=True)
+    private_key_escaped = html.escape(str(credentials["pem"]).replace("\n", "\\n"), quote=False)
     app_id_escaped = html.escape(str(credentials["id"]), quote=True)
     webhook_secret_escaped = html.escape(str(credentials["webhook_secret"]), quote=True)
     app_name_escaped = html.escape(str(credentials.get("name", "Stampbot")), quote=True)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceTrueWithFalse, occurrence: 2, definition_name: setup_callback
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -662,7 +662,7 @@
     # 4. The alternative (file download) adds friction without meaningful security gain
     #    since the credentials must be transmitted to the browser regardless
     private_key_escaped = html.escape(str(credentials["pem"]).replace("\n", "\\n"), quote=True)
-    app_id_escaped = html.escape(str(credentials["id"]), quote=True)
+    app_id_escaped = html.escape(str(credentials["id"]), quote=False)
     webhook_secret_escaped = html.escape(str(credentials["webhook_secret"]), quote=True)
     app_name_escaped = html.escape(str(credentials.get("name", "Stampbot")), quote=True)
     app_slug = urllib.parse.quote(str(credentials.get("slug", "stampbot")), safe="")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceTrueWithFalse, occurrence: 3, definition_name: setup_callback
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -663,7 +663,7 @@
     #    since the credentials must be transmitted to the browser regardless
     private_key_escaped = html.escape(str(credentials["pem"]).replace("\n", "\\n"), quote=True)
     app_id_escaped = html.escape(str(credentials["id"]), quote=True)
-    webhook_secret_escaped = html.escape(str(credentials["webhook_secret"]), quote=True)
+    webhook_secret_escaped = html.escape(str(credentials["webhook_secret"]), quote=False)
     app_name_escaped = html.escape(str(credentials.get("name", "Stampbot")), quote=True)
     app_slug = urllib.parse.quote(str(credentials.get("slug", "stampbot")), safe="")
     installation_url = html.escape(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceTrueWithFalse, occurrence: 4, definition_name: setup_callback
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -664,7 +664,7 @@
     private_key_escaped = html.escape(str(credentials["pem"]).replace("\n", "\\n"), quote=True)
     app_id_escaped = html.escape(str(credentials["id"]), quote=True)
     webhook_secret_escaped = html.escape(str(credentials["webhook_secret"]), quote=True)
-    app_name_escaped = html.escape(str(credentials.get("name", "Stampbot")), quote=True)
+    app_name_escaped = html.escape(str(credentials.get("name", "Stampbot")), quote=False)
     app_slug = urllib.parse.quote(str(credentials.get("slug", "stampbot")), safe="")
     installation_url = html.escape(
         f"https://github.com/settings/apps/{app_slug}/installations",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/ReplaceTrueWithFalse, occurrence: 5, definition_name: setup_callback
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -668,7 +668,7 @@
     app_slug = urllib.parse.quote(str(credentials.get("slug", "stampbot")), safe="")
     installation_url = html.escape(
         f"https://github.com/settings/apps/{app_slug}/installations",
-        quote=True,
+        quote=False,
     )
 
     html_content = f"""
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceTrueWithFalse, occurrence: 6, definition_name: setup_status
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -761,6 +761,6 @@
     _require_setup_access()
     return {
         "configured": is_configured(),
-        "setup_enabled": True,
+        "setup_enabled": False,
     }
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
...................................F
=================================== FAILURES ===================================
________ TestSetupEndpointsUnconfigured.test_setup_status_omits_app_id _________

self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7e01c838e8b0>

    def test_setup_status_omits_app_id(self):
        """Test active setup status exposes no App identifier."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            response = TestClient(app, raise_server_exceptions=False).get("/setup/status")
    
        assert response.status_code == 200
>       assert response.json() == {"configured": False, "setup_enabled": True}
E       AssertionError: assert {'configured'...abled': False} == {'configured'...nabled': True}
E         
E         Omitting 1 identical items, use -vv to show
E         Differing items:
E         {'setup_enabled': False} != {'setup_enabled': True}
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           {
E               'configured': False,
E         -     'setup_enabled': True,
E         ?                      ^^^
E         +     'setup_enabled': False,
E         ?                      ^^^^
E           }

tests/test_setup_endpoints.py:317: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/setup/status \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:42:29.712492Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/status "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_setup_status_omits_app_id - AssertionError: assert {'configured'...abled': False} == {'configured'...nabled': True}
  
  Omitting 1 identical items, use -vv to show
  Differing items:
  {'setup_enabled': False} != {'setup_enabled': True}
  
  Full diff: (-: missing in left side, +: extra in left side)
    {
        'configured': False,
  -     'setup_enabled': True,
  ?                      ^^^
  +     'setup_enabled': False,
  ?                      ^^^^
    }
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 323 passed, 3 deselected, 1 warning in 2.32s
operator: core/ReplaceFalseWithTrue, occurrence: 0, definition_name: _setup_requested
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -72,7 +72,7 @@
 
 def _setup_requested() -> bool:
     """Return whether the operator explicitly enabled setup."""
-    return bool(settings.get("setup_enabled", False))
+    return bool(settings.get("setup_enabled", True))
 
 
 def _setup_allowed_when_configured() -> bool:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceFalseWithTrue, occurrence: 1, definition_name: _setup_allowed_when_configured
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -77,7 +77,7 @@
 
 def _setup_allowed_when_configured() -> bool:
     """Return whether setup was explicitly reopened after configuration."""
-    return bool(settings.get("setup_allow_configured", False))
+    return bool(settings.get("setup_allow_configured", True))
 
 
 def _setup_is_available(configured: bool | None = None) -> bool:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceFalseWithTrue, occurrence: 2, definition_name: _setup_is_available
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -83,7 +83,7 @@
 def _setup_is_available(configured: bool | None = None) -> bool:
     """Return whether setup routes should be available for the current state."""
     if not _setup_requested():
-        return False
+        return True
 
     app_configured = is_configured() if configured is None else configured
     return not app_configured or _setup_allowed_when_configured()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.F
=================================== FAILURES ===================================
_______________ test_ready_endpoint_unconfigured_setup_disabled ________________

test_client = <starlette.testclient.TestClient object at 0x71ba385658c0>

    def test_ready_endpoint_unconfigured_setup_disabled(test_client: TestClient):
        """Test readiness is 503 only when unconfigured and setup is disabled."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.get.side_effect = lambda key, default=None: {
                "setup_enabled": False,
                "setup_allow_configured": False,
            }.get(key, default)
            response = test_client.get("/ready")
>       assert response.status_code == 503
E       assert 200 == 503
E        +  where 200 = <Response [200 OK]>.status_code

tests/test_main.py:145: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:18:33.232497Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_unconfigured_setup_disabled - assert 200 == 503
 +  where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 217 passed, 3 deselected, 1 warning in 1.59s
operator: core/ReplaceFalseWithTrue, occurrence: 3, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -147,7 +147,7 @@
     configured = is_configured()
     if not configured:
         logger.warning("GitHub App credentials not configured.")
-        if _setup_is_available(configured=False):
+        if _setup_is_available(configured=True):
             logger.info("Setup is enabled; visit /setup to create your GitHub App")
         else:
             logger.warning("Setup is disabled; enable it explicitly to provision the App")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceFalseWithTrue, occurrence: 4, definition_name: root
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -347,7 +347,7 @@
         Redirect to /setup if unconfigured, otherwise JSON status response.
     """
     configured = is_configured()
-    if not configured and _setup_is_available(configured=False):
+    if not configured and _setup_is_available(configured=True):
         return RedirectResponse(url="/setup", status_code=307)
 
     return JSONResponse(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..............................F
=================================== FAILURES ===================================
_ TestSetupEndpointsUnconfigured.test_root_redirects_to_setup_when_unconfigured _

self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7066f56c74d0>

    def test_root_redirects_to_setup_when_unconfigured(self):
        """Test root redirects to /setup when not configured."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            client = TestClient(app, raise_server_exceptions=False)
            response = client.get("/", follow_redirects=False)
    
>           assert response.status_code == 307
E           assert 200 == 307
E            +  where 200 = <Response [200 OK]>.status_code

tests/test_setup_endpoints.py:187: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:51:21.717277Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_root_redirects_to_setup_when_unconfigured - assert 200 == 307
 +  where 200 = <Response [200 OK]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 318 passed, 3 deselected, 1 warning in 2.32s
operator: core/ReplaceAndWithOr, occurrence: 0, definition_name: _require_setup_access
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -97,7 +97,7 @@
     """
     if not _setup_requested():
         raise HTTPException(status_code=403, detail="Setup is disabled")
-    if is_configured() and not _setup_allowed_when_configured():
+    if is_configured() or not _setup_allowed_when_configured():
         raise HTTPException(
             status_code=403,
             detail="Setup is closed because Stampbot is already configured",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
...........................F
=================================== FAILURES ===================================
_____ TestSetupEndpointsConfigured.test_setup_can_be_deliberately_reopened _____

self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x773e977a7820>

    def test_setup_can_be_deliberately_reopened(self):
        """Test configured setup requires the second explicit opt-in."""
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(
                mock_settings,
                enabled=True,
                base_url="https://configured.example.test",
                allow_configured=True,
            )
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            response = TestClient(app, raise_server_exceptions=False).get("/setup")
    
>       assert response.status_code == 200
E       assert 403 == 200
E        +  where 403 = <Response [403 Forbidden]>.status_code

tests/test_setup_endpoints.py:120: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/setup \"HTTP/1.1 403 Forbidden\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:46:18.127906Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 403 Forbidden"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_can_be_deliberately_reopened - assert 403 == 200
 +  where 403 = <Response [403 Forbidden]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 315 passed, 3 deselected, 1 warning in 2.36s
operator: core/ReplaceAndWithOr, occurrence: 1, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -221,7 +221,7 @@
             continue
         if match is Match.FULL:
             return route_path
-        if match is Match.PARTIAL and partial_match is None:
+        if match is Match.PARTIAL or partial_match is None:
             # A path match with the wrong method will become a 405 response.
             partial_match = route_path
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x7c8dd9b01fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='136948684433664'>, 'http_request_duration_seconds': ...='136948684434336'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='136948684434672'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
            response = test_client.post(path, content=b"request body")
            assert response.status_code == 404
    
>       assert _endpoint_labels(http_metric_spies["http_requests_total"]) == [
            UNMATCHED_ENDPOINT,
            UNMATCHED_ENDPOINT,
        ]
E       AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
E         
E         At index 0 diff: '/openapi.json' != 'unmatched'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         -     'unmatched',
E         -     'unmatched',
E         +     '/openapi.json',
E         +     '/openapi.json',
E           ]

tests/test_http_metrics.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:57:34.751560Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found" [httpx]
2026-11-04T03:57:34.754479Z [info     ] HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found"
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - AssertionError: assert ['/openapi.js...openapi.json'] == ['unmatched', 'unmatched']
  
  At index 0 diff: '/openapi.json' != 'unmatched'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     'unmatched',
  -     'unmatched',
  +     '/openapi.json',
  +     '/openapi.json',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.46s
operator: core/ReplaceAndWithOr, occurrence: 2, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -327,7 +327,7 @@
                     # entry is the original client (proxies append to the right).
                     client_ip = raw.split(",")[0].strip()
 
-            if not client_ip and scope.get("client"):
+            if not client_ip or scope.get("client"):
                 client_ip = scope["client"][0]
 
             if client_ip:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________

    def test_logging_middleware_respects_configured_header():
        """Test that logging_middleware uses the configured client_ip_header setting."""
        from unittest.mock import patch
    
        import structlog.contextvars
    
        from stampbot.main import app
    
        with patch("stampbot.main.settings") as mock_settings:
            mock_settings.get = lambda key, default=None: (
                "X-Real-IP" if key == "client_ip_header" else default
            )
    
            client = TestClient(app)
            captured: dict[str, str | None] = {}
    
            original_bind = structlog.contextvars.bind_contextvars
    
            def capturing_bind(**kw: object) -> None:
                captured.update({k: str(v) for k, v in kw.items()})
                original_bind(**kw)
    
            with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
                client.get("/health", headers={"X-Real-IP": "198.51.100.7"})
    
>           assert captured.get("client_ip") == "198.51.100.7"
E           AssertionError: assert 'testclient' == '198.51.100.7'
E             
E             - 198.51.100.7
E             + testclient

tests/test_main.py:594: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/health \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:29:40.350198Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - AssertionError: assert 'testclient' == '198.51.100.7'
  
  - 198.51.100.7
  + testclient
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 239 passed, 3 deselected, 1 warning in 2.21s
operator: core/ReplaceAndWithOr, occurrence: 3, definition_name: root
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -347,7 +347,7 @@
         Redirect to /setup if unconfigured, otherwise JSON status response.
     """
     configured = is_configured()
-    if not configured and _setup_is_available(configured=False):
+    if not configured or _setup_is_available(configured=False):
         return RedirectResponse(url="/setup", status_code=307)
 
     return JSONResponse(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.41s
operator: core/ReplaceAndWithOr, occurrence: 4, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -497,7 +497,7 @@
 
     # Refuse a declared-oversize body without reading any of it.
     content_length = request.headers.get("content-length")
-    if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
+    if content_length or int(content_length) > MAX_WEBHOOK_BODY_SIZE:
         errors_total.labels(error_type="payload_too_large").inc()
         raise HTTPException(status_code=413, detail="Request body too large")
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x765259f829c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 413 == 401
E        +  where 413 = <Response [413 Request Entity Too Large]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 413 Request Entity Too Large\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:20:19.409721Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 413 Request Entity Too Large"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 413 == 401
 +  where 413 = <Response [413 Request Entity Too Large]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.10s
operator: core/ReplaceOrWithAnd, occurrence: 0, definition_name: _setup_is_available
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -86,7 +86,7 @@
         return False
 
     app_configured = is_configured() if configured is None else configured
-    return not app_configured or _setup_allowed_when_configured()
+    return not app_configured and _setup_allowed_when_configured()
 
 
 def _require_setup_access() -> None:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
F
=================================== FAILURES ===================================
________________ test_ready_endpoint_unconfigured_setup_enabled ________________

test_client = <starlette.testclient.TestClient object at 0x7c03ae891260>

    def test_ready_endpoint_unconfigured_setup_enabled(test_client: TestClient):
        """Test readiness is 200 in setup mode so /setup stays reachable.
    
        An unconfigured pod with setup mode on must stay in the Service endpoints,
        otherwise the operator can never reach /setup to configure it.
        """
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.get.side_effect = lambda key, default=None: {
                "setup_enabled": True,
                "setup_allow_configured": False,
            }.get(key, default)
            response = test_client.get("/ready")
>       assert response.status_code == 200
E       assert 503 == 200
E        +  where 503 = <Response [503 Service Unavailable]>.status_code

tests/test_main.py:128: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 503 Service Unavailable\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:40:02.158039Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 503 Service Unavailable"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_unconfigured_setup_enabled - assert 503 == 200
 +  where 503 = <Response [503 Service Unavailable]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 216 passed, 3 deselected, 1 warning in 1.59s
operator: core/ReplaceOrWithAnd, occurrence: 1, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -225,7 +225,7 @@
             # A path match with the wrong method will become a 405 response.
             partial_match = route_path
 
-    return partial_match or UNMATCHED_ENDPOINT
+    return partial_match and UNMATCHED_ENDPOINT
 
 
 @app.middleware("http")
........................................................................ [ 15%]
........................................................................ [ 30%]
...........................................F
=================================== FAILURES ===================================
_______________ test_route_without_a_path_uses_bounded_fallback ________________

    def test_route_without_a_path_uses_bounded_fallback() -> None:
        """Custom framework routes without path templates cannot leak raw paths."""
        route = MagicMock()
        route.matches.return_value = (object(), {"route": SimpleNamespace(path=None)})
        request = Request(
            {
                "type": "http",
                "app": SimpleNamespace(routes=[route]),
                "path": "/raw-user-controlled-path",
            }
        )
    
>       assert _metric_endpoint_label(request) == UNMATCHED_ENDPOINT
E       AssertionError: assert None == 'unmatched'
E        +  where None = _metric_endpoint_label(<starlette.requests.Request object at 0x7ad829619450>)

tests/test_http_metrics.py:84: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_route_without_a_path_uses_bounded_fallback - AssertionError: assert None == 'unmatched'
 +  where None = _metric_endpoint_label(<starlette.requests.Request object at 0x7ad829619450>)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 187 passed, 3 deselected, 1 warning in 1.48s
operator: core/ReplaceOrWithAnd, occurrence: 2, definition_name: ready
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -394,7 +394,7 @@
     configured = is_configured()
     setup_enabled = _setup_is_available(configured=configured)
     checks = {"configured": configured, "setup_enabled": setup_enabled}
-    is_ready = configured or setup_enabled
+    is_ready = configured and setup_enabled
     return JSONResponse(
         status_code=200 if is_ready else 503,
         content={"status": "ready" if is_ready else "not ready", "checks": checks},
........................................................................ [ 15%]
........................................................................ [ 30%]
.......................................................................F [ 45%]

=================================== FAILURES ===================================
________________________ test_ready_endpoint_configured ________________________

test_client = <starlette.testclient.TestClient object at 0x729a3fc75370>

    def test_ready_endpoint_configured(test_client: TestClient):
        """Test readiness endpoint returns 200 when the app is configured."""
        with patch("stampbot.main.is_configured", return_value=True):
            response = test_client.get("/ready")
>       assert response.status_code == 200
E       assert 503 == 200
E        +  where 503 = <Response [503 Service Unavailable]>.status_code

tests/test_main.py:107: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 503 Service Unavailable\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:33:07.707877Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 503 Service Unavailable"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_configured - assert 503 == 200
 +  where 503 = <Response [503 Service Unavailable]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 215 passed, 3 deselected, 1 warning in 1.59s
operator: core/ReplaceOrWithAnd, occurrence: 3, definition_name: _delivery_id_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -417,7 +417,7 @@
     """
     if not raw:
         return None
-    if len(raw) > MAX_DELIVERY_ID_LENGTH or not DELIVERY_ID_PATTERN.fullmatch(raw):
+    if len(raw) > MAX_DELIVERY_ID_LENGTH and not DELIVERY_ID_PATTERN.fullmatch(raw):
         return None
     return raw
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
............................F
=================================== FAILURES ===================================
_ test_webhook_drops_malformed_delivery_id[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] _

test_client = <starlette.testclient.TestClient object at 0x7fca7baeb130>
header = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

    @pytest.mark.parametrize("header", ["", "x" * 65, "not a guid!", "../../etc"])
    def test_webhook_drops_malformed_delivery_id(test_client: TestClient, header: str):
        """Test a delivery header that is not a GUID is neither logged nor forwarded."""
        body = json.dumps({"zen": "test"}).encode()
        seen_context: dict[str, object] = {}
    
        async def fake_handle(event_type, payload, delivery_id=None):
            seen_context.update(structlog.contextvars.get_contextvars())
            return {"status": "ok", "message": event_type, "delivery": delivery_id}
    
        with patch("stampbot.main.webhook_handler.handle_event", side_effect=fake_handle):
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-GitHub-Delivery": header,
                    "X-Hub-Signature-256": _signed_ping(body),
                },
            )
    
        assert response.status_code == 200
>       assert response.json()["delivery"] is None
E       AssertionError: assert 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' is None

tests/test_main.py:719: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:37:48.431351Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_drops_malformed_delivery_id[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] - AssertionError: assert 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 244 passed, 3 deselected, 1 warning in 2.20s
operator: core/ReplaceOrWithAnd, occurrence: 4, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -537,7 +537,7 @@
         )
         duration = time.time() - start_time
 
-        webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
+        webhook_processing_duration_seconds.labels(event_type=x_github_event and "unknown").observe(
             duration
         )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.29s
operator: core/ReplaceContinueWithBreak, occurrence: 0, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -218,7 +218,7 @@
         matched_route = child_scope.get("route", route)
         route_path = getattr(matched_route, "path", None)
         if not isinstance(route_path, str):
-            continue
+            break
         if match is Match.FULL:
             return route_path
         if match is Match.PARTIAL and partial_match is None:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ExceptionReplacer, occurrence: 0, definition_name: _trusted_setup_base_url
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -113,7 +113,7 @@
     configured_base_url = settings.get("base_url", "")
     try:
         return validate_base_url(configured_base_url)
-    except ValueError as error:
+    except CosmicRayTestingException as error:
         logger.warning("Setup base URL is missing or invalid: %s", error)
         raise HTTPException(
             status_code=503,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
................................F
=================================== FAILURES ===================================
____ TestSetupEndpointsUnconfigured.test_setup_requires_configured_base_url ____

self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7b410756c180>

    def test_setup_requires_configured_base_url(self):
        """Test request headers cannot supply a missing trusted setup URL."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            client = TestClient(app, raise_server_exceptions=False)
            response = client.get(
                "/setup",
                headers={
                    "Host": "internal-service:8000",
                    "X-Forwarded-Proto": "https",
                    "X-Forwarded-Host": "untrusted.example.test",
                },
            )
    
>           assert response.status_code == 503
E           assert 500 == 503
E            +  where 500 = <Response [500 Internal Server Error]>.status_code

tests/test_setup_endpoints.py:242: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/setup \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:17:20.335763Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_setup_requires_configured_base_url - assert 500 == 503
 +  where 500 = <Response [500 Internal Server Error]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 320 passed, 3 deselected, 1 warning in 2.30s
operator: core/ExceptionReplacer, occurrence: 1, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -518,7 +518,7 @@
     # consume an exhausted stream.
     try:
         payload = json.loads(body)
-    except Exception as e:
+    except CosmicRayTestingException as e:
         errors_total.labels(error_type="payload_invalid").inc()
         logger.error("Failed to parse webhook payload: %s", e)
         raise HTTPException(status_code=400, detail="Invalid JSON payload") from None
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.............F
=================================== FAILURES ===================================
__________________________ test_webhook_invalid_json ___________________________

request = <starlette.requests.Request object at 0x72967d6a71d0>
x_github_event = 'ping'
x_hub_signature_256 = 'sha256=dc481541cb8394ab16852fc7c7cbce720b045e7456801ae3998a45f260ad2915'
x_github_delivery = None

    @app.post("/webhook")
    async def webhook(
        request: Request,
        x_github_event: str = Header(None, alias="X-GitHub-Event"),
        x_hub_signature_256: str = Header(None, alias="X-Hub-Signature-256"),
        x_github_delivery: str = Header(None, alias="X-GitHub-Delivery"),
    ) -> dict[str, Any]:
        """GitHub webhook endpoint.
    
        Args:
            request: FastAPI request.
            x_github_event: GitHub event type.
            x_hub_signature_256: Webhook signature.
            x_github_delivery: GitHub delivery GUID, bound to logs and traces after
                the signature is verified so a delivery in GitHub's log can be
                matched to Stampbot's records.
    
        Returns:
            Response dictionary with status and message.
    
        Raises:
            HTTPException: 400 if event header or JSON invalid, 401 if signature invalid,
                413 if body too large, 503 if not configured, 500 on internal error.
        """
        # Check if app is configured
        if not is_configured():
            raise HTTPException(
                status_code=503,
                detail="Stampbot not configured. Visit /setup to complete setup.",
            )
    
        if not x_github_event:
            errors_total.labels(error_type="missing_event").inc()
            raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
    
        # Refuse a declared-oversize body without reading any of it.
        content_length = request.headers.get("content-length")
        if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
            errors_total.labels(error_type="payload_too_large").inc()
            raise HTTPException(status_code=413, detail="Request body too large")
    
        # Read the raw body for signature verification, enforcing the limit as the
        # bytes arrive rather than after they are all in memory.
        body = await _read_body_within_limit(request)
    
        # Verify signature
        if not webhook_handler.verify_signature(body, x_hub_signature_256):
            webhook_signature_validations_total.labels(result="invalid").inc()
            errors_total.labels(error_type="signature_invalid").inc()
            logger.warning("Invalid webhook signature")
            raise HTTPException(status_code=401, detail="Invalid signature")
    
        webhook_signature_validations_total.labels(result="valid").inc()
    
        # Parse the bytes we already read. Calling request.json() here would try to
        # consume an exhausted stream.
        try:
>           payload = json.loads(body)
                      ^^^^^^^^^^^^^^^^

stampbot/main.py:520: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/json/__init__.py:352: in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/json/decoder.py:345: in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <json.decoder.JSONDecoder object at 0x729681de0590>, s = 'not valid json'
idx = 0

    def raw_decode(self, s, idx=0):
        """Decode a JSON document from ``s`` (a ``str`` beginning with
        a JSON document) and return a 2-tuple of the Python
        representation and the index in ``s`` where the document ended.
    
        This can be used to decode a JSON document from a string that may
        have extraneous data at the end.
    
        """
        try:
            obj, end = self.scan_once(s, idx)
        except StopIteration as err:
>           raise JSONDecodeError("Expecting value", s, err.value) from None
E           json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

/usr/lib/python3.14/json/decoder.py:363: JSONDecodeError

The above exception was the direct cause of the following exception:

test_client = <starlette.testclient.TestClient object at 0x72967b87fbd0>

    def test_webhook_invalid_json(test_client: TestClient):
        """Test webhook rejects invalid JSON payload."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        # Create a valid signature for invalid JSON
        body = b"not valid json"
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
>       response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )

tests/test_main.py:326: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.requests.Request object at 0x72967d6a71d0>
x_github_event = 'ping'
x_hub_signature_256 = 'sha256=dc481541cb8394ab16852fc7c7cbce720b045e7456801ae3998a45f260ad2915'
x_github_delivery = None

    @app.post("/webhook")
    async def webhook(
        request: Request,
        x_github_event: str = Header(None, alias="X-GitHub-Event"),
        x_hub_signature_256: str = Header(None, alias="X-Hub-Signature-256"),
        x_github_delivery: str = Header(None, alias="X-GitHub-Delivery"),
    ) -> dict[str, Any]:
        """GitHub webhook endpoint.
    
        Args:
            request: FastAPI request.
            x_github_event: GitHub event type.
            x_hub_signature_256: Webhook signature.
            x_github_delivery: GitHub delivery GUID, bound to logs and traces after
                the signature is verified so a delivery in GitHub's log can be
                matched to Stampbot's records.
    
        Returns:
            Response dictionary with status and message.
    
        Raises:
            HTTPException: 400 if event header or JSON invalid, 401 if signature invalid,
                413 if body too large, 503 if not configured, 500 on internal error.
        """
        # Check if app is configured
        if not is_configured():
            raise HTTPException(
                status_code=503,
                detail="Stampbot not configured. Visit /setup to complete setup.",
            )
    
        if not x_github_event:
            errors_total.labels(error_type="missing_event").inc()
            raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
    
        # Refuse a declared-oversize body without reading any of it.
        content_length = request.headers.get("content-length")
        if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
            errors_total.labels(error_type="payload_too_large").inc()
            raise HTTPException(status_code=413, detail="Request body too large")
    
        # Read the raw body for signature verification, enforcing the limit as the
        # bytes arrive rather than after they are all in memory.
        body = await _read_body_within_limit(request)
    
        # Verify signature
        if not webhook_handler.verify_signature(body, x_hub_signature_256):
            webhook_signature_validations_total.labels(result="invalid").inc()
            errors_total.labels(error_type="signature_invalid").inc()
            logger.warning("Invalid webhook signature")
            raise HTTPException(status_code=401, detail="Invalid signature")
    
        webhook_signature_validations_total.labels(result="valid").inc()
    
        # Parse the bytes we already read. Calling request.json() here would try to
        # consume an exhausted stream.
        try:
            payload = json.loads(body)
>       except CosmicRayTestingException as e:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/main.py:521: NameError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:23:21.424910Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_invalid_json - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 229 passed, 3 deselected, 1 warning in 2.25s
operator: core/ExceptionReplacer, occurrence: 2, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -542,7 +542,7 @@
         )
 
         return result
-    except Exception as e:
+    except CosmicRayTestingException as e:
         errors_total.labels(error_type="webhook_handler_error").inc()
         sanitized = _sanitize_error(e)
         logger.error("Error handling webhook event: %s", sanitized, extra={"error": sanitized})
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........................F
=================================== FAILURES ===================================
________________________ test_webhook_handler_exception ________________________

request = <starlette.requests.Request object at 0x7abc1449d910>
x_github_event = 'ping'
x_hub_signature_256 = 'sha256=595bde46b32458346f405d2b0b88ade07c42ddd464fa4cb8df965915cfa558d2'
x_github_delivery = None

    @app.post("/webhook")
    async def webhook(
        request: Request,
        x_github_event: str = Header(None, alias="X-GitHub-Event"),
        x_hub_signature_256: str = Header(None, alias="X-Hub-Signature-256"),
        x_github_delivery: str = Header(None, alias="X-GitHub-Delivery"),
    ) -> dict[str, Any]:
        """GitHub webhook endpoint.
    
        Args:
            request: FastAPI request.
            x_github_event: GitHub event type.
            x_hub_signature_256: Webhook signature.
            x_github_delivery: GitHub delivery GUID, bound to logs and traces after
                the signature is verified so a delivery in GitHub's log can be
                matched to Stampbot's records.
    
        Returns:
            Response dictionary with status and message.
    
        Raises:
            HTTPException: 400 if event header or JSON invalid, 401 if signature invalid,
                413 if body too large, 503 if not configured, 500 on internal error.
        """
        # Check if app is configured
        if not is_configured():
            raise HTTPException(
                status_code=503,
                detail="Stampbot not configured. Visit /setup to complete setup.",
            )
    
        if not x_github_event:
            errors_total.labels(error_type="missing_event").inc()
            raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
    
        # Refuse a declared-oversize body without reading any of it.
        content_length = request.headers.get("content-length")
        if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
            errors_total.labels(error_type="payload_too_large").inc()
            raise HTTPException(status_code=413, detail="Request body too large")
    
        # Read the raw body for signature verification, enforcing the limit as the
        # bytes arrive rather than after they are all in memory.
        body = await _read_body_within_limit(request)
    
        # Verify signature
        if not webhook_handler.verify_signature(body, x_hub_signature_256):
            webhook_signature_validations_total.labels(result="invalid").inc()
            errors_total.labels(error_type="signature_invalid").inc()
            logger.warning("Invalid webhook signature")
            raise HTTPException(status_code=401, detail="Invalid signature")
    
        webhook_signature_validations_total.labels(result="valid").inc()
    
        # Parse the bytes we already read. Calling request.json() here would try to
        # consume an exhausted stream.
        try:
            payload = json.loads(body)
        except Exception as e:
            errors_total.labels(error_type="payload_invalid").inc()
            logger.error("Failed to parse webhook payload: %s", e)
            raise HTTPException(status_code=400, detail="Invalid JSON payload") from None
    
        # The header is only trusted once the HMAC check has passed. Bound length
        # keeps a malformed value out of log storage.
        delivery_id = _delivery_id_label(x_github_delivery)
        if delivery_id:
            structlog.contextvars.bind_contextvars(delivery_id=delivery_id)
    
        # Handle event with timing
        try:
            start_time = time.time()
>           result = await webhook_handler.handle_event(
                x_github_event, payload, delivery_id=delivery_id
            )

stampbot/main.py:535: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <AsyncMock name='handle_event' id='134948210562720'>
args = ('ping', {'zen': 'test'}), kwargs = {'delivery_id': None}

    async def _execute_mock_call(self, /, *args, **kwargs):
        # This is nearly just like super(), except for special handling
        # of coroutines
    
        _call = _Call((args, kwargs), two=True)
        self.await_count += 1
        self.await_args = _call
        self.await_args_list.append(_call)
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: Unexpected error

/usr/lib/python3.14/unittest/mock.py:2333: Exception

The above exception was the direct cause of the following exception:

test_client = <starlette.testclient.TestClient object at 0x7abc12adf460>

    def test_webhook_handler_exception(test_client: TestClient):
        """Test webhook returns 500 when handler raises exception."""
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "test"}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        with patch("stampbot.main.webhook_handler.handle_event") as mock_handle:
            mock_handle.side_effect = Exception("Unexpected error")
    
>           response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-Hub-Signature-256": signature,
                },
            )

tests/test_main.py:648: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.requests.Request object at 0x7abc1449d910>
x_github_event = 'ping'
x_hub_signature_256 = 'sha256=595bde46b32458346f405d2b0b88ade07c42ddd464fa4cb8df965915cfa558d2'
x_github_delivery = None

    @app.post("/webhook")
    async def webhook(
        request: Request,
        x_github_event: str = Header(None, alias="X-GitHub-Event"),
        x_hub_signature_256: str = Header(None, alias="X-Hub-Signature-256"),
        x_github_delivery: str = Header(None, alias="X-GitHub-Delivery"),
    ) -> dict[str, Any]:
        """GitHub webhook endpoint.
    
        Args:
            request: FastAPI request.
            x_github_event: GitHub event type.
            x_hub_signature_256: Webhook signature.
            x_github_delivery: GitHub delivery GUID, bound to logs and traces after
                the signature is verified so a delivery in GitHub's log can be
                matched to Stampbot's records.
    
        Returns:
            Response dictionary with status and message.
    
        Raises:
            HTTPException: 400 if event header or JSON invalid, 401 if signature invalid,
                413 if body too large, 503 if not configured, 500 on internal error.
        """
        # Check if app is configured
        if not is_configured():
            raise HTTPException(
                status_code=503,
                detail="Stampbot not configured. Visit /setup to complete setup.",
            )
    
        if not x_github_event:
            errors_total.labels(error_type="missing_event").inc()
            raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
    
        # Refuse a declared-oversize body without reading any of it.
        content_length = request.headers.get("content-length")
        if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
            errors_total.labels(error_type="payload_too_large").inc()
            raise HTTPException(status_code=413, detail="Request body too large")
    
        # Read the raw body for signature verification, enforcing the limit as the
        # bytes arrive rather than after they are all in memory.
        body = await _read_body_within_limit(request)
    
        # Verify signature
        if not webhook_handler.verify_signature(body, x_hub_signature_256):
            webhook_signature_validations_total.labels(result="invalid").inc()
            errors_total.labels(error_type="signature_invalid").inc()
            logger.warning("Invalid webhook signature")
            raise HTTPException(status_code=401, detail="Invalid signature")
    
        webhook_signature_validations_total.labels(result="valid").inc()
    
        # Parse the bytes we already read. Calling request.json() here would try to
        # consume an exhausted stream.
        try:
            payload = json.loads(body)
        except Exception as e:
            errors_total.labels(error_type="payload_invalid").inc()
            logger.error("Failed to parse webhook payload: %s", e)
            raise HTTPException(status_code=400, detail="Invalid JSON payload") from None
    
        # The header is only trusted once the HMAC check has passed. Bound length
        # keeps a malformed value out of log storage.
        delivery_id = _delivery_id_label(x_github_delivery)
        if delivery_id:
            structlog.contextvars.bind_contextvars(delivery_id=delivery_id)
    
        # Handle event with timing
        try:
            start_time = time.time()
            result = await webhook_handler.handle_event(
                x_github_event, payload, delivery_id=delivery_id
            )
            duration = time.time() - start_time
    
            webhook_processing_duration_seconds.labels(event_type=x_github_event or "unknown").observe(
                duration
            )
    
            return result
>       except CosmicRayTestingException as e:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/main.py:545: NameError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:33:11.025738Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_handler_exception - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 241 passed, 3 deselected, 1 warning in 2.33s
operator: core/ExceptionReplacer, occurrence: 3, definition_name: setup_callback
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -650,7 +650,7 @@
 
     try:
         credentials = await exchange_code_for_credentials(code)
-    except Exception as e:
+    except CosmicRayTestingException as e:
         logger.error("Failed to exchange code for credentials: %s", _sanitize_error(e))
         raise HTTPException(status_code=500, detail="Failed to complete setup") from None
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.......................................F
=================================== FAILURES ===================================
____________ TestSetupCallback.test_callback_handles_exchange_error ____________

code = 'bad-code'

    @app.get("/setup/callback")
    async def setup_callback(code: str) -> Response:
        """Handle callback from GitHub after app creation.
    
        Args:
            code: Temporary code from GitHub to exchange for credentials.
    
        Returns:
            HTML page with credentials and setup instructions.
    
        Raises:
            HTTPException: If setup disabled (403) or code exchange fails (500).
        """
        _require_setup_access()
    
        try:
>           credentials = await exchange_code_for_credentials(code)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/main.py:652: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <AsyncMock name='exchange_code_for_credentials' id='139495720524768'>
args = ('bad-code',), kwargs = {}

    async def _execute_mock_call(self, /, *args, **kwargs):
        # This is nearly just like super(), except for special handling
        # of coroutines
    
        _call = _Call((args, kwargs), two=True)
        self.await_count += 1
        self.await_args = _call
        self.await_args_list.append(_call)
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               Exception: API error

/usr/lib/python3.14/unittest/mock.py:2333: Exception

The above exception was the direct cause of the following exception:

self = <tests.test_setup_endpoints.TestSetupCallback object at 0x7ededf9103e0>
test_client = <starlette.testclient.TestClient object at 0x7ededf7fbf00>

    def test_callback_handles_exchange_error(self, test_client):
        """Test callback handles exchange error gracefully."""
        with (
            patch("stampbot.main._require_setup_access"),
            patch(
                "stampbot.main.exchange_code_for_credentials",
                new_callable=AsyncMock,
                side_effect=Exception("API error"),
            ),
        ):
>           response = test_client.get("/setup/callback?code=bad-code")
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_setup_endpoints.py:404: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

code = 'bad-code'

    @app.get("/setup/callback")
    async def setup_callback(code: str) -> Response:
        """Handle callback from GitHub after app creation.
    
        Args:
            code: Temporary code from GitHub to exchange for credentials.
    
        Returns:
            HTML page with credentials and setup instructions.
    
        Raises:
            HTTPException: If setup disabled (403) or code exchange fails (500).
        """
        _require_setup_access()
    
        try:
            credentials = await exchange_code_for_credentials(code)
>       except CosmicRayTestingException as e:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/main.py:653: NameError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/setup/callback?code=bad-code \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:17:31.050212Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback?code=bad-code "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupCallback::test_callback_handles_exchange_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 327 passed, 3 deselected, 1 warning in 2.50s
operator: core/NumberReplacer, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1025 * 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 1, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1023 * 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.40s
operator: core/NumberReplacer, occurrence: 2, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 * 1025  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.30s
operator: core/NumberReplacer, occurrence: 3, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -55,7 +55,7 @@
 configure_telemetry()
 
 # Security limits
-MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
+MAX_WEBHOOK_BODY_SIZE = 1024 * 1023  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
 MAX_DELIVERY_ID_LENGTH = 64
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 4, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -57,7 +57,7 @@
 # Security limits
 MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
-MAX_DELIVERY_ID_LENGTH = 64
+MAX_DELIVERY_ID_LENGTH = 65
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
 UNMATCHED_ENDPOINT = "unmatched"
 SETUP_HTML_HEADERS = {
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
............................F
=================================== FAILURES ===================================
_ test_webhook_drops_malformed_delivery_id[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] _

test_client = <starlette.testclient.TestClient object at 0x79a93ca5f130>
header = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

    @pytest.mark.parametrize("header", ["", "x" * 65, "not a guid!", "../../etc"])
    def test_webhook_drops_malformed_delivery_id(test_client: TestClient, header: str):
        """Test a delivery header that is not a GUID is neither logged nor forwarded."""
        body = json.dumps({"zen": "test"}).encode()
        seen_context: dict[str, object] = {}
    
        async def fake_handle(event_type, payload, delivery_id=None):
            seen_context.update(structlog.contextvars.get_contextvars())
            return {"status": "ok", "message": event_type, "delivery": delivery_id}
    
        with patch("stampbot.main.webhook_handler.handle_event", side_effect=fake_handle):
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-GitHub-Delivery": header,
                    "X-Hub-Signature-256": _signed_ping(body),
                },
            )
    
        assert response.status_code == 200
>       assert response.json()["delivery"] is None
E       AssertionError: assert 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' is None

tests/test_main.py:719: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 200 OK\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:35:10.987303Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_drops_malformed_delivery_id[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] - AssertionError: assert 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' is None
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 244 passed, 3 deselected, 1 warning in 2.15s
operator: core/NumberReplacer, occurrence: 5, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -57,7 +57,7 @@
 # Security limits
 MAX_WEBHOOK_BODY_SIZE = 1024 * 1024  # 1MB - GitHub webhooks are typically much smaller
 # X-GitHub-Delivery is a GUID such as 72d3162e-cc78-11e3-81ab-4c9367dc0958.
-MAX_DELIVERY_ID_LENGTH = 64
+MAX_DELIVERY_ID_LENGTH = 63
 DELIVERY_ID_PATTERN = re.compile(r"[A-Za-z0-9-]+")
 UNMATCHED_ENDPOINT = "unmatched"
 SETUP_HTML_HEADERS = {
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 6, definition_name: _require_setup_access
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -96,7 +96,7 @@
         HTTPException: If setup is disabled or automatically closed.
     """
     if not _setup_requested():
-        raise HTTPException(status_code=403, detail="Setup is disabled")
+        raise HTTPException(status_code= 404, detail="Setup is disabled")
     if is_configured() and not _setup_allowed_when_configured():
         raise HTTPException(
             status_code=403,
........................................................................ [ 15%]
........................................................................ [ 30%]
...............................................F
=================================== FAILURES ===================================
________________ test_setup_routes_keep_distinct_static_labels _________________

test_client = <starlette.testclient.TestClient object at 0x774edb00e520>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='131180563875888'>, 'http_request_duration_seconds': ...='131180563876560'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='131180563876896'>, ...}

    def test_setup_routes_keep_distinct_static_labels(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Setup and callback traffic remains distinguishable by route."""
        setup_response = test_client.get("/setup")
        callback_response = test_client.get("/setup/callback")
    
>       assert setup_response.status_code == 403
E       assert 404 == 403
E        +  where 404 = <Response [404 Not Found]>.status_code

tests/test_http_metrics.py:143: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:41:43.736645Z [info     ] HTTP Request: GET http://testserver/setup "HTTP/1.1 404 Not Found" [httpx]
2026-11-04T04:41:43.739866Z [info     ] HTTP Request: GET http://testserver/setup/callback "HTTP/1.1 422 Unprocessable Entity" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 404 Not Found"
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback "HTTP/1.1 422 Unprocessable Entity"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_setup_routes_keep_distinct_static_labels - assert 404 == 403
 +  where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 191 passed, 3 deselected, 1 warning in 1.55s
operator: core/NumberReplacer, occurrence: 7, definition_name: _require_setup_access
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -96,7 +96,7 @@
         HTTPException: If setup is disabled or automatically closed.
     """
     if not _setup_requested():
-        raise HTTPException(status_code=403, detail="Setup is disabled")
+        raise HTTPException(status_code= 402, detail="Setup is disabled")
     if is_configured() and not _setup_allowed_when_configured():
         raise HTTPException(
             status_code=403,
........................................................................ [ 15%]
........................................................................ [ 30%]
...............................................F
=================================== FAILURES ===================================
________________ test_setup_routes_keep_distinct_static_labels _________________

test_client = <starlette.testclient.TestClient object at 0x718ed441e520>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='124858258835504'>, 'http_request_duration_seconds': ...='124858258836176'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='124858258836512'>, ...}

    def test_setup_routes_keep_distinct_static_labels(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Setup and callback traffic remains distinguishable by route."""
        setup_response = test_client.get("/setup")
        callback_response = test_client.get("/setup/callback")
    
>       assert setup_response.status_code == 403
E       assert 402 == 403
E        +  where 402 = <Response [402 Payment Required]>.status_code

tests/test_http_metrics.py:143: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:36:11.814738Z [info     ] HTTP Request: GET http://testserver/setup "HTTP/1.1 402 Payment Required" [httpx]
2026-11-04T04:36:11.817644Z [info     ] HTTP Request: GET http://testserver/setup/callback "HTTP/1.1 422 Unprocessable Entity" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 402 Payment Required"
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback "HTTP/1.1 422 Unprocessable Entity"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_setup_routes_keep_distinct_static_labels - assert 402 == 403
 +  where 402 = <Response [402 Payment Required]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 191 passed, 3 deselected, 1 warning in 1.54s
operator: core/NumberReplacer, occurrence: 8, definition_name: _require_setup_access
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -99,7 +99,7 @@
         raise HTTPException(status_code=403, detail="Setup is disabled")
     if is_configured() and not _setup_allowed_when_configured():
         raise HTTPException(
-            status_code=403,
+            status_code= 404,
             detail="Setup is closed because Stampbot is already configured",
         )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..........................F
=================================== FAILURES ===================================
_____ TestSetupEndpointsConfigured.test_setup_auto_closes_when_configured ______

self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7d9ecc2c7390>

    def test_setup_auto_closes_when_configured(self):
        """Test an initial-setup opt-in does not keep setup open after configuration."""
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(
                mock_settings,
                enabled=True,
                base_url="https://configured.example.test",
            )
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            response = TestClient(app, raise_server_exceptions=False).get("/setup")
    
>       assert response.status_code == 403
E       assert 404 == 403
E        +  where 404 = <Response [404 Not Found]>.status_code

tests/test_setup_endpoints.py:98: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/setup \"HTTP/1.1 404 Not Found\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:58:33.122529Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_auto_closes_when_configured - assert 404 == 403
 +  where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 314 passed, 3 deselected, 1 warning in 2.26s
operator: core/NumberReplacer, occurrence: 9, definition_name: _require_setup_access
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -99,7 +99,7 @@
         raise HTTPException(status_code=403, detail="Setup is disabled")
     if is_configured() and not _setup_allowed_when_configured():
         raise HTTPException(
-            status_code=403,
+            status_code= 402,
             detail="Setup is closed because Stampbot is already configured",
         )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..........................F
=================================== FAILURES ===================================
_____ TestSetupEndpointsConfigured.test_setup_auto_closes_when_configured ______

self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x75edbfda7390>

    def test_setup_auto_closes_when_configured(self):
        """Test an initial-setup opt-in does not keep setup open after configuration."""
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(
                mock_settings,
                enabled=True,
                base_url="https://configured.example.test",
            )
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            response = TestClient(app, raise_server_exceptions=False).get("/setup")
    
>       assert response.status_code == 403
E       assert 402 == 403
E        +  where 402 = <Response [402 Payment Required]>.status_code

tests/test_setup_endpoints.py:98: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/setup \"HTTP/1.1 402 Payment Required\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:29:20.294607Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 402 Payment Required"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_auto_closes_when_configured - assert 402 == 403
 +  where 402 = <Response [402 Payment Required]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 314 passed, 3 deselected, 1 warning in 2.31s
operator: core/NumberReplacer, occurrence: 10, definition_name: _trusted_setup_base_url
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -116,7 +116,7 @@
     except ValueError as error:
         logger.warning("Setup base URL is missing or invalid: %s", error)
         raise HTTPException(
-            status_code=503,
+            status_code= 504,
             detail=("Setup requires STAMPBOT_BASE_URL to be set to Stampbot's trusted public URL"),
         ) from None
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
................................F
=================================== FAILURES ===================================
____ TestSetupEndpointsUnconfigured.test_setup_requires_configured_base_url ____

self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7d3c75290180>

    def test_setup_requires_configured_base_url(self):
        """Test request headers cannot supply a missing trusted setup URL."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            client = TestClient(app, raise_server_exceptions=False)
            response = client.get(
                "/setup",
                headers={
                    "Host": "internal-service:8000",
                    "X-Forwarded-Proto": "https",
                    "X-Forwarded-Host": "untrusted.example.test",
                },
            )
    
>           assert response.status_code == 503
E           assert 504 == 503
E            +  where 504 = <Response [504 Gateway Timeout]>.status_code

tests/test_setup_endpoints.py:242: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Setup base URL is missing or invalid: Invalid base_url: must be configured", "client_ip": "testclient", "logger": "stampbot.main", "level": "warning", "timestamp": "2026-11-04T03:20:22.836154Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "warning"}
{"event": "HTTP Request: GET http://testserver/setup \"HTTP/1.1 504 Gateway Timeout\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:20:22.836698Z"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.main:main.py:117 {'event': 'Setup base URL is missing or invalid: Invalid base_url: must be configured', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'warning', 'timestamp': '2026-11-04T03:20:22.836154Z'}
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 504 Gateway Timeout"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_setup_requires_configured_base_url - assert 504 == 503
 +  where 504 = <Response [504 Gateway Timeout]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 320 passed, 3 deselected, 1 warning in 2.31s
operator: core/NumberReplacer, occurrence: 11, definition_name: _trusted_setup_base_url
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -116,7 +116,7 @@
     except ValueError as error:
         logger.warning("Setup base URL is missing or invalid: %s", error)
         raise HTTPException(
-            status_code=503,
+            status_code= 502,
             detail=("Setup requires STAMPBOT_BASE_URL to be set to Stampbot's trusted public URL"),
         ) from None
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
................................F
=================================== FAILURES ===================================
____ TestSetupEndpointsUnconfigured.test_setup_requires_configured_base_url ____

self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x72feea2f8180>

    def test_setup_requires_configured_base_url(self):
        """Test request headers cannot supply a missing trusted setup URL."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            client = TestClient(app, raise_server_exceptions=False)
            response = client.get(
                "/setup",
                headers={
                    "Host": "internal-service:8000",
                    "X-Forwarded-Proto": "https",
                    "X-Forwarded-Host": "untrusted.example.test",
                },
            )
    
>           assert response.status_code == 503
E           assert 502 == 503
E            +  where 502 = <Response [502 Bad Gateway]>.status_code

tests/test_setup_endpoints.py:242: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Setup base URL is missing or invalid: Invalid base_url: must be configured", "client_ip": "testclient", "logger": "stampbot.main", "level": "warning", "timestamp": "2026-11-04T04:04:36.265372Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "warning"}
{"event": "HTTP Request: GET http://testserver/setup \"HTTP/1.1 502 Bad Gateway\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:04:36.265922Z"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.main:main.py:117 {'event': 'Setup base URL is missing or invalid: Invalid base_url: must be configured', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'warning', 'timestamp': '2026-11-04T04:04:36.265372Z'}
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 502 Bad Gateway"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_setup_requires_configured_base_url - assert 502 == 503
 +  where 502 = <Response [502 Bad Gateway]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 320 passed, 3 deselected, 1 warning in 2.32s
operator: core/NumberReplacer, occurrence: 12, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 2 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/NumberReplacer, occurrence: 13, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 0 <= metrics_port <= 65535:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......F
=================================== FAILURES ===================================
_ test_lifespan_rejects_invalid_metrics_listener[127.0.0.1-0-metrics_port must be between 1 and 65535] _

metrics_host = '127.0.0.1', metrics_port = 0
message = 'metrics_port must be between 1 and 65535'

    @pytest.mark.parametrize(
        ("metrics_host", "metrics_port", "message"),
        [
            ("", 9090, "metrics_host must not be empty"),
            ("127.0.0.1", 0, "metrics_port must be between 1 and 65535"),
            ("127.0.0.1", 65536, "metrics_port must be between 1 and 65535"),
        ],
    )
    def test_lifespan_rejects_invalid_metrics_listener(
        metrics_host: str,
        metrics_port: int,
        message: str,
    ):
        """Test that invalid dedicated-listener settings fail startup."""
        from stampbot.main import app
    
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = metrics_host
            mock_settings.metrics_port = metrics_port
    
>           with pytest.raises(RuntimeError, match=message):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE RuntimeError

tests/test_main.py:251: Failed
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:21:22.692400Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:21:22.692526Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"extra": {"host": "127.0.0.1", "port": 0}, "event": "Prometheus metrics listener started", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:21:22.692941Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "Shutting down stampbot", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T04:21:23.193758Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:21:22.692400Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:21:22.692526Z'}
INFO     stampbot.main:main.py:167 {'extra': {'host': '127.0.0.1', 'port': 0}, 'event': 'Prometheus metrics listener started', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:21:22.692941Z'}
INFO     stampbot.main:main.py:178 {'event': 'Shutting down stampbot', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T04:21:23.193758Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_rejects_invalid_metrics_listener[127.0.0.1-0-metrics_port must be between 1 and 65535] - Failed: DID NOT RAISE RuntimeError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 223 passed, 3 deselected, 1 warning in 2.62s
operator: core/NumberReplacer, occurrence: 14, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 <= metrics_port <= 65536:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........F
=================================== FAILURES ===================================
_ test_lifespan_rejects_invalid_metrics_listener[127.0.0.1-65536-metrics_port must be between 1 and 65535] _

metrics_host = '127.0.0.1', metrics_port = 65536
message = 'metrics_port must be between 1 and 65535'

    @pytest.mark.parametrize(
        ("metrics_host", "metrics_port", "message"),
        [
            ("", 9090, "metrics_host must not be empty"),
            ("127.0.0.1", 0, "metrics_port must be between 1 and 65535"),
            ("127.0.0.1", 65536, "metrics_port must be between 1 and 65535"),
        ],
    )
    def test_lifespan_rejects_invalid_metrics_listener(
        metrics_host: str,
        metrics_port: int,
        message: str,
    ):
        """Test that invalid dedicated-listener settings fail startup."""
        from stampbot.main import app
    
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
            mock_settings.metrics_enabled = True
            mock_settings.metrics_host = metrics_host
            mock_settings.metrics_port = metrics_port
    
            with pytest.raises(RuntimeError, match=message):
>               with TestClient(app):
                     ^^^^^^^^^^^^^^^

tests/test_main.py:252: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:572: in __enter__
    portal.call(self.wait_startup)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:347: in call
    return cast(T_Retval, self.start_task_soon(func, *args).result())
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:450: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:608: in wait_startup
    await receive()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:599: in receive
    self.task.result()
/usr/lib/python3.14/concurrent/futures/_base.py:443: in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/concurrent/futures/_base.py:395: in __get_result
    raise self._exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:589: in lifespan
    await self.app(scope, self.stream_receive.receive, self.stream_send.send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1181: in __call__
    await lifespan(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_runtime.py:238: in lifespan
    await app(scope, wrapped_receive, wrapped_send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:151: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:103: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:49: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2798: in app
    await self.lifespan(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:661: in lifespan
    async with self.lifespan_context(app) as maybe_state:
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/contextlib.py:214: in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:166: in lifespan
    metrics_runtime = start_metrics_server(metrics_host, metrics_port)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/metrics.py:194: in start_metrics_server
    server, thread = start_http_server(port=port, addr=host, registry=registry)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/prometheus_client/exposition.py:260: in start_wsgi_server
    httpd = make_server(addr, port, app, TmpServer, handler_class=_SilentHandler)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/wsgiref/simple_server.py:150: in make_server
    server = server_class((host, port), handler_class)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/socketserver.py:457: in __init__
    self.server_bind()
/usr/lib/python3.14/wsgiref/simple_server.py:50: in server_bind
    HTTPServer.server_bind(self)
/usr/lib/python3.14/http/server.py:148: in server_bind
    socketserver.TCPServer.server_bind(self)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <prometheus_client.exposition.start_wsgi_server.<locals>.TmpServer object at 0x78a0439970e0>

    def server_bind(self):
        """Called by constructor to bind the socket.
    
        May be overridden.
    
        """
        if self.allow_reuse_address and hasattr(socket, "SO_REUSEADDR"):
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # Since Linux 6.12.9, SO_REUSEPORT is not allowed
        # on other address families than AF_INET/AF_INET6.
        if (
            self.allow_reuse_port and hasattr(socket, "SO_REUSEPORT")
            and self.address_family in (socket.AF_INET, socket.AF_INET6)
        ):
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
>       self.socket.bind(self.server_address)
E       OverflowError: bind(): port must be 0-65535.

/usr/lib/python3.14/socketserver.py:478: OverflowError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:38:54.407102Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:38:54.407253Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:38:54.407102Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:38:54.407253Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_rejects_invalid_metrics_listener[127.0.0.1-65536-metrics_port must be between 1 and 65535] - OverflowError: bind(): port must be 0-65535.
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 224 passed, 3 deselected, 1 warning in 2.21s
operator: core/NumberReplacer, occurrence: 15, definition_name: lifespan
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -159,7 +159,7 @@
         metrics_port = int(settings.metrics_port)
         if not metrics_host.strip():
             raise RuntimeError("metrics_host must not be empty")
-        if not 1 <= metrics_port <= 65535:
+        if not 1 <= metrics_port <= 65534:
             raise RuntimeError("metrics_port must be between 1 and 65535")
         if metrics_port == int(settings.port):
             raise RuntimeError("metrics_port must differ from the public HTTP port")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/NumberReplacer, occurrence: 16, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -325,7 +325,7 @@
                 if raw:
                     # X-Forwarded-For may be a comma-separated list; the leftmost
                     # entry is the original client (proxies append to the right).
-                    client_ip = raw.split(",")[0].strip()
+                    client_ip = raw.split(",")[ 1].strip()
 
             if not client_ip and scope.get("client"):
                 client_ip = scope["client"][0]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......................F
=================================== FAILURES ===================================
______________ test_logging_middleware_respects_configured_header ______________

    def test_logging_middleware_respects_configured_header():
        """Test that logging_middleware uses the configured client_ip_header setting."""
        from unittest.mock import patch
    
        import structlog.contextvars
    
        from stampbot.main import app
    
        with patch("stampbot.main.settings") as mock_settings:
            mock_settings.get = lambda key, default=None: (
                "X-Real-IP" if key == "client_ip_header" else default
            )
    
            client = TestClient(app)
            captured: dict[str, str | None] = {}
    
            original_bind = structlog.contextvars.bind_contextvars
    
            def capturing_bind(**kw: object) -> None:
                captured.update({k: str(v) for k, v in kw.items()})
                original_bind(**kw)
    
            with patch("stampbot.main.structlog.contextvars.bind_contextvars", capturing_bind):
>               client.get("/health", headers={"X-Real-IP": "198.51.100.7"})

tests/test_main.py:592: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1053: in get
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.main.LoggingMiddleware object at 0x75f3f7f58590>
scope = {'type': 'http', 'http_version': '1.1', 'method': 'GET', 'path': '/health', ...}
receive = <function _TestClientTransport.handle_request.<locals>.receive at 0x75f3f68031c0>
send = <function ServerErrorMiddleware.__call__.<locals>._send at 0x75f3f6998720>

    async def __call__(self, scope: Any, receive: Any, send: Any) -> None:
        """Process an ASGI event, binding client IP to the structlog context.
    
        For HTTP scopes the middleware clears any inherited structlog context,
        extracts the real client IP from the configured forwarding header (or
        the direct connection address as a fallback), and binds it so that
        every log record emitted during the request includes ``client_ip``.
    
        Non-HTTP scopes (lifespan, WebSocket) are passed through unchanged.
    
        Args:
            scope: ASGI connection scope.
            receive: ASGI receive callable.
            send: ASGI send callable.
        """
        if scope["type"] == "http":
            structlog.contextvars.clear_contextvars()
    
            client_ip: str | None = None
            client_ip_header: str = settings.get("client_ip_header", "X-Forwarded-For")
    
            if client_ip_header:
                raw = Request(scope).headers.get(client_ip_header)
                if raw:
                    # X-Forwarded-For may be a comma-separated list; the leftmost
                    # entry is the original client (proxies append to the right).
>                   client_ip = raw.split(",")[ 1].strip()
                                ^^^^^^^^^^^^^^^^^^
E                   IndexError: list index out of range

stampbot/main.py:328: IndexError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/health \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:50:26.749623Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_logging_middleware_respects_configured_header - IndexError: list index out of range
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 239 passed, 3 deselected, 1 warning in 2.28s
operator: core/NumberReplacer, occurrence: 17, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -325,7 +325,7 @@
                 if raw:
                     # X-Forwarded-For may be a comma-separated list; the leftmost
                     # entry is the original client (proxies append to the right).
-                    client_ip = raw.split(",")[0].strip()
+                    client_ip = raw.split(",")[ -1].strip()
 
             if not client_ip and scope.get("client"):
                 client_ip = scope["client"][0]
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 18, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -328,7 +328,7 @@
                     client_ip = raw.split(",")[0].strip()
 
             if not client_ip and scope.get("client"):
-                client_ip = scope["client"][0]
+                client_ip = scope["client"][ 1]
 
             if client_ip:
                 structlog.contextvars.bind_contextvars(client_ip=client_ip)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/NumberReplacer, occurrence: 19, definition_name: __call__
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -328,7 +328,7 @@
                     client_ip = raw.split(",")[0].strip()
 
             if not client_ip and scope.get("client"):
-                client_ip = scope["client"][0]
+                client_ip = scope["client"][ -1]
 
             if client_ip:
                 structlog.contextvars.bind_contextvars(client_ip=client_ip)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 20, definition_name: root
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -348,7 +348,7 @@
     """
     configured = is_configured()
     if not configured and _setup_is_available(configured=False):
-        return RedirectResponse(url="/setup", status_code=307)
+        return RedirectResponse(url="/setup", status_code= 308)
 
     return JSONResponse(
         content={"app": "stampbot", "version": APP_VERSION, "status": "running"},
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..............................F
=================================== FAILURES ===================================
_ TestSetupEndpointsUnconfigured.test_root_redirects_to_setup_when_unconfigured _

self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x75cff72af4d0>

    def test_root_redirects_to_setup_when_unconfigured(self):
        """Test root redirects to /setup when not configured."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            client = TestClient(app, raise_server_exceptions=False)
            response = client.get("/", follow_redirects=False)
    
>           assert response.status_code == 307
E           assert 308 == 307
E            +  where 308 = <Response [308 Permanent Redirect]>.status_code

tests/test_setup_endpoints.py:187: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ \"HTTP/1.1 308 Permanent Redirect\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:59:13.217974Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 308 Permanent Redirect"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_root_redirects_to_setup_when_unconfigured - assert 308 == 307
 +  where 308 = <Response [308 Permanent Redirect]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 318 passed, 3 deselected, 1 warning in 2.27s
operator: core/NumberReplacer, occurrence: 21, definition_name: root
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -348,7 +348,7 @@
     """
     configured = is_configured()
     if not configured and _setup_is_available(configured=False):
-        return RedirectResponse(url="/setup", status_code=307)
+        return RedirectResponse(url="/setup", status_code= 306)
 
     return JSONResponse(
         content={"app": "stampbot", "version": APP_VERSION, "status": "running"},
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
..............................F
=================================== FAILURES ===================================
_ TestSetupEndpointsUnconfigured.test_root_redirects_to_setup_when_unconfigured _

self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x787c14cb34d0>

    def test_root_redirects_to_setup_when_unconfigured(self):
        """Test root redirects to /setup when not configured."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
            mock_settings.app_name = "stampbot"
            mock_settings.host = "0.0.0.0"
            mock_settings.port = 8000
            mock_settings.log_level = "INFO"
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            client = TestClient(app, raise_server_exceptions=False)
            response = client.get("/", follow_redirects=False)
    
>           assert response.status_code == 307
E           assert 306 == 307
E            +  where 306 = <Response [306 ]>.status_code

tests/test_setup_endpoints.py:187: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ \"HTTP/1.1 306 \"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:14:15.801291Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 306 "
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_root_redirects_to_setup_when_unconfigured - assert 306 == 307
 +  where 306 = <Response [306 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 318 passed, 3 deselected, 1 warning in 2.29s
operator: core/NumberReplacer, occurrence: 22, definition_name: ready
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -396,7 +396,7 @@
     checks = {"configured": configured, "setup_enabled": setup_enabled}
     is_ready = configured or setup_enabled
     return JSONResponse(
-        status_code=200 if is_ready else 503,
+        status_code= 201 if is_ready else 503,
         content={"status": "ready" if is_ready else "not ready", "checks": checks},
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.......................................................................F [ 45%]

=================================== FAILURES ===================================
________________________ test_ready_endpoint_configured ________________________

test_client = <starlette.testclient.TestClient object at 0x726644781370>

    def test_ready_endpoint_configured(test_client: TestClient):
        """Test readiness endpoint returns 200 when the app is configured."""
        with patch("stampbot.main.is_configured", return_value=True):
            response = test_client.get("/ready")
>       assert response.status_code == 200
E       assert 201 == 200
E        +  where 201 = <Response [201 Created]>.status_code

tests/test_main.py:107: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 201 Created\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:24:07.628206Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 201 Created"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_configured - assert 201 == 200
 +  where 201 = <Response [201 Created]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 215 passed, 3 deselected, 1 warning in 1.58s
operator: core/NumberReplacer, occurrence: 23, definition_name: ready
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -396,7 +396,7 @@
     checks = {"configured": configured, "setup_enabled": setup_enabled}
     is_ready = configured or setup_enabled
     return JSONResponse(
-        status_code=200 if is_ready else 503,
+        status_code= 199 if is_ready else 503,
         content={"status": "ready" if is_ready else "not ready", "checks": checks},
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.......................................................................F [ 45%]

=================================== FAILURES ===================================
________________________ test_ready_endpoint_configured ________________________

test_client = <starlette.testclient.TestClient object at 0x7bffdc97d370>

    def test_ready_endpoint_configured(test_client: TestClient):
        """Test readiness endpoint returns 200 when the app is configured."""
        with patch("stampbot.main.is_configured", return_value=True):
            response = test_client.get("/ready")
>       assert response.status_code == 200
E       assert 199 == 200
E        +  where 199 = <Response [199 ]>.status_code

tests/test_main.py:107: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 199 \"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:12:31.655620Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 199 "
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_configured - assert 199 == 200
 +  where 199 = <Response [199 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 215 passed, 3 deselected, 1 warning in 1.57s
operator: core/NumberReplacer, occurrence: 24, definition_name: ready
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -396,7 +396,7 @@
     checks = {"configured": configured, "setup_enabled": setup_enabled}
     is_ready = configured or setup_enabled
     return JSONResponse(
-        status_code=200 if is_ready else 503,
+        status_code=200 if is_ready else 504,
         content={"status": "ready" if is_ready else "not ready", "checks": checks},
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.F
=================================== FAILURES ===================================
_______________ test_ready_endpoint_unconfigured_setup_disabled ________________

test_client = <starlette.testclient.TestClient object at 0x759e63b998c0>

    def test_ready_endpoint_unconfigured_setup_disabled(test_client: TestClient):
        """Test readiness is 503 only when unconfigured and setup is disabled."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.get.side_effect = lambda key, default=None: {
                "setup_enabled": False,
                "setup_allow_configured": False,
            }.get(key, default)
            response = test_client.get("/ready")
>       assert response.status_code == 503
E       assert 504 == 503
E        +  where 504 = <Response [504 Gateway Timeout]>.status_code

tests/test_main.py:145: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 504 Gateway Timeout\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:00:08.039857Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 504 Gateway Timeout"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_unconfigured_setup_disabled - assert 504 == 503
 +  where 504 = <Response [504 Gateway Timeout]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 217 passed, 3 deselected, 1 warning in 1.54s
operator: core/NumberReplacer, occurrence: 25, definition_name: ready
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -396,7 +396,7 @@
     checks = {"configured": configured, "setup_enabled": setup_enabled}
     is_ready = configured or setup_enabled
     return JSONResponse(
-        status_code=200 if is_ready else 503,
+        status_code=200 if is_ready else 502,
         content={"status": "ready" if is_ready else "not ready", "checks": checks},
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.F
=================================== FAILURES ===================================
_______________ test_ready_endpoint_unconfigured_setup_disabled ________________

test_client = <starlette.testclient.TestClient object at 0x7d8fa1c4d8c0>

    def test_ready_endpoint_unconfigured_setup_disabled(test_client: TestClient):
        """Test readiness is 503 only when unconfigured and setup is disabled."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            mock_settings.get.side_effect = lambda key, default=None: {
                "setup_enabled": False,
                "setup_allow_configured": False,
            }.get(key, default)
            response = test_client.get("/ready")
>       assert response.status_code == 503
E       assert 502 == 503
E        +  where 502 = <Response [502 Bad Gateway]>.status_code

tests/test_main.py:145: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 502 Bad Gateway\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:36:38.623728Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 502 Bad Gateway"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_unconfigured_setup_disabled - assert 502 == 503
 +  where 502 = <Response [502 Bad Gateway]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 217 passed, 3 deselected, 1 warning in 1.57s
operator: core/NumberReplacer, occurrence: 26, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -455,7 +455,7 @@
     async for chunk in request.stream():
         if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
-            raise HTTPException(status_code=413, detail="Request body too large")
+            raise HTTPException(status_code= 414, detail="Request body too large")
         body += chunk
     return bytes(body)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................F
=================================== FAILURES ===================================
_______ test_webhook_streamed_body_over_limit_is_rejected_while_reading ________

    async def test_webhook_streamed_body_over_limit_is_rejected_while_reading():
        """An oversize streamed body is refused during the read, not after it.
    
        Without a ``Content-Length`` the pre-read check cannot see the size, so this
        is the case that used to reach ``Request.body()`` and buffer everything. The
        offered-bytes assertion is the regression guard: it fails if the limit moves
        back to a check that runs once the whole body is already in memory.
        """
        chunk_size = 64 * 1024
        total = 4 * MAX_WEBHOOK_BODY_SIZE
        offered = 0
    
        async def body():
            nonlocal offered
            for _ in range(total // chunk_size):
                offered += chunk_size
                yield b"x" * chunk_size
    
        with patch.object(webhook_handler, "verify_signature", return_value=True) as verify:
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
>       assert response.status_code == 413
E       assert 414 == 413
E        +  where 414 = <Response [414 Request-URI Too Long]>.status_code

tests/test_main.py:437: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 414 Request-URI Too Long\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:16:34.071560Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 414 Request-URI Too Long"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_over_limit_is_rejected_while_reading - assert 414 == 413
 +  where 414 = <Response [414 Request-URI Too Long]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 232 passed, 3 deselected, 1 warning in 2.13s
operator: core/NumberReplacer, occurrence: 27, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -455,7 +455,7 @@
     async for chunk in request.stream():
         if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
-            raise HTTPException(status_code=413, detail="Request body too large")
+            raise HTTPException(status_code= 412, detail="Request body too large")
         body += chunk
     return bytes(body)
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................F
=================================== FAILURES ===================================
_______ test_webhook_streamed_body_over_limit_is_rejected_while_reading ________

    async def test_webhook_streamed_body_over_limit_is_rejected_while_reading():
        """An oversize streamed body is refused during the read, not after it.
    
        Without a ``Content-Length`` the pre-read check cannot see the size, so this
        is the case that used to reach ``Request.body()`` and buffer everything. The
        offered-bytes assertion is the regression guard: it fails if the limit moves
        back to a check that runs once the whole body is already in memory.
        """
        chunk_size = 64 * 1024
        total = 4 * MAX_WEBHOOK_BODY_SIZE
        offered = 0
    
        async def body():
            nonlocal offered
            for _ in range(total // chunk_size):
                offered += chunk_size
                yield b"x" * chunk_size
    
        with patch.object(webhook_handler, "verify_signature", return_value=True) as verify:
            async with _asgi_client() as client:
                response = await client.post(
                    "/webhook",
                    content=body(),
                    headers={
                        "Content-Type": "application/json",
                        "X-GitHub-Event": "ping",
                        "X-Hub-Signature-256": "sha256=fake",
                    },
                )
    
>       assert response.status_code == 413
E       assert 412 == 413
E        +  where 412 = <Response [412 Precondition Failed]>.status_code

tests/test_main.py:437: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 412 Precondition Failed\"", "client_ip": "127.0.0.1", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:16:49.792090Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1740 HTTP Request: POST http://testserver/webhook "HTTP/1.1 412 Precondition Failed"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_streamed_body_over_limit_is_rejected_while_reading - assert 412 == 413
 +  where 412 = <Response [412 Precondition Failed]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 232 passed, 3 deselected, 1 warning in 2.13s
operator: core/NumberReplacer, occurrence: 28, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -487,7 +487,7 @@
     # Check if app is configured
     if not is_configured():
         raise HTTPException(
-            status_code=503,
+            status_code= 504,
             detail="Stampbot not configured. Visit /setup to complete setup.",
         )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
....................................F
=================================== FAILURES ===================================
__ TestSetupEndpointsUnconfigured.test_webhook_returns_503_when_unconfigured ___

self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7dd25a7aa360>

    def test_webhook_returns_503_when_unconfigured(self):
        """Test webhook returns 503 when not configured."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            client = TestClient(app, raise_server_exceptions=False)
            response = client.post("/webhook", json={})
    
>           assert response.status_code == 503
E           assert 504 == 503
E            +  where 504 = <Response [504 Gateway Timeout]>.status_code

tests/test_setup_endpoints.py:335: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 504 Gateway Timeout\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:17:40.840654Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 504 Gateway Timeout"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_webhook_returns_503_when_unconfigured - assert 504 == 503
 +  where 504 = <Response [504 Gateway Timeout]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 324 passed, 3 deselected, 1 warning in 2.34s
operator: core/NumberReplacer, occurrence: 29, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -487,7 +487,7 @@
     # Check if app is configured
     if not is_configured():
         raise HTTPException(
-            status_code=503,
+            status_code= 502,
             detail="Stampbot not configured. Visit /setup to complete setup.",
         )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
....................................F
=================================== FAILURES ===================================
__ TestSetupEndpointsUnconfigured.test_webhook_returns_503_when_unconfigured ___

self = <tests.test_setup_endpoints.TestSetupEndpointsUnconfigured object at 0x7fbac7796360>

    def test_webhook_returns_503_when_unconfigured(self):
        """Test webhook returns 503 when not configured."""
        with (
            patch("stampbot.main.is_configured", return_value=False),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            client = TestClient(app, raise_server_exceptions=False)
            response = client.post("/webhook", json={})
    
>           assert response.status_code == 503
E           assert 502 == 503
E            +  where 502 = <Response [502 Bad Gateway]>.status_code

tests/test_setup_endpoints.py:335: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 502 Bad Gateway\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:56:05.527491Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 502 Bad Gateway"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsUnconfigured::test_webhook_returns_503_when_unconfigured - assert 502 == 503
 +  where 502 = <Response [502 Bad Gateway]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 324 passed, 3 deselected, 1 warning in 2.33s
operator: core/NumberReplacer, occurrence: 30, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -493,7 +493,7 @@
 
     if not x_github_event:
         errors_total.labels(error_type="missing_event").inc()
-        raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
+        raise HTTPException(status_code= 401, detail="Missing X-GitHub-Event header")
 
     # Refuse a declared-oversize body without reading any of it.
     content_length = request.headers.get("content-length")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...........F
=================================== FAILURES ===================================
______________________ test_webhook_missing_event_header _______________________

test_client = <starlette.testclient.TestClient object at 0x7a0aa05978a0>

    def test_webhook_missing_event_header(test_client: TestClient):
        """Test webhook rejects requests without X-GitHub-Event header."""
        response = test_client.post(
            "/webhook",
            json={"zen": "test"},
            headers={"X-Hub-Signature-256": "sha256=invalid"},
        )
>       assert response.status_code == 400
E       assert 401 == 400
E        +  where 401 = <Response [401 Unauthorized]>.status_code

tests/test_main.py:290: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 401 Unauthorized\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:19:54.616428Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_missing_event_header - assert 401 == 400
 +  where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 227 passed, 3 deselected, 1 warning in 2.09s
operator: core/NumberReplacer, occurrence: 31, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -493,7 +493,7 @@
 
     if not x_github_event:
         errors_total.labels(error_type="missing_event").inc()
-        raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")
+        raise HTTPException(status_code= 399, detail="Missing X-GitHub-Event header")
 
     # Refuse a declared-oversize body without reading any of it.
     content_length = request.headers.get("content-length")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...........F
=================================== FAILURES ===================================
______________________ test_webhook_missing_event_header _______________________

test_client = <starlette.testclient.TestClient object at 0x7ed9ccbb38a0>

    def test_webhook_missing_event_header(test_client: TestClient):
        """Test webhook rejects requests without X-GitHub-Event header."""
        response = test_client.post(
            "/webhook",
            json={"zen": "test"},
            headers={"X-Hub-Signature-256": "sha256=invalid"},
        )
>       assert response.status_code == 400
E       assert 399 == 400
E        +  where 399 = <Response [399 ]>.status_code

tests/test_main.py:290: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 399 \"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:20:16.185866Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 399 "
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_missing_event_header - assert 399 == 400
 +  where 399 = <Response [399 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 227 passed, 3 deselected, 1 warning in 2.10s
operator: core/NumberReplacer, occurrence: 32, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -499,7 +499,7 @@
     content_length = request.headers.get("content-length")
     if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
         errors_total.labels(error_type="payload_too_large").inc()
-        raise HTTPException(status_code=413, detail="Request body too large")
+        raise HTTPException(status_code= 414, detail="Request body too large")
 
     # Read the raw body for signature verification, enforcing the limit as the
     # bytes arrive rather than after they are all in memory.
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________

test_client = <starlette.testclient.TestClient object at 0x72641815f9b0>

    def test_webhook_content_length_too_large(test_client: TestClient):
        """A declared-oversize body is refused without reading it."""
        response = test_client.post(
            "/webhook",
            content=b"x",  # Small actual body
            headers={
                "Content-Type": "application/json",
                "Content-Length": str(MAX_WEBHOOK_BODY_SIZE + 1),
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=fake",
            },
        )
>       assert response.status_code == 413
E       assert 414 == 413
E        +  where 414 = <Response [414 Request-URI Too Long]>.status_code

tests/test_main.py:383: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 414 Request-URI Too Long\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:09:23.342684Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 414 Request-URI Too Long"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 414 == 413
 +  where 414 = <Response [414 Request-URI Too Long]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 231 passed, 3 deselected, 1 warning in 2.14s
operator: core/NumberReplacer, occurrence: 33, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -499,7 +499,7 @@
     content_length = request.headers.get("content-length")
     if content_length and int(content_length) > MAX_WEBHOOK_BODY_SIZE:
         errors_total.labels(error_type="payload_too_large").inc()
-        raise HTTPException(status_code=413, detail="Request body too large")
+        raise HTTPException(status_code= 412, detail="Request body too large")
 
     # Read the raw body for signature verification, enforcing the limit as the
     # bytes arrive rather than after they are all in memory.
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............F
=================================== FAILURES ===================================
____________________ test_webhook_content_length_too_large _____________________

test_client = <starlette.testclient.TestClient object at 0x7879ccfbf9b0>

    def test_webhook_content_length_too_large(test_client: TestClient):
        """A declared-oversize body is refused without reading it."""
        response = test_client.post(
            "/webhook",
            content=b"x",  # Small actual body
            headers={
                "Content-Type": "application/json",
                "Content-Length": str(MAX_WEBHOOK_BODY_SIZE + 1),
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=fake",
            },
        )
>       assert response.status_code == 413
E       assert 412 == 413
E        +  where 412 = <Response [412 Precondition Failed]>.status_code

tests/test_main.py:383: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 412 Precondition Failed\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:46:54.467956Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 412 Precondition Failed"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_content_length_too_large - assert 412 == 413
 +  where 412 = <Response [412 Precondition Failed]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 231 passed, 3 deselected, 1 warning in 2.18s
operator: core/NumberReplacer, occurrence: 34, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -510,7 +510,7 @@
         webhook_signature_validations_total.labels(result="invalid").inc()
         errors_total.labels(error_type="signature_invalid").inc()
         logger.warning("Invalid webhook signature")
-        raise HTTPException(status_code=401, detail="Invalid signature")
+        raise HTTPException(status_code= 402, detail="Invalid signature")
 
     webhook_signature_validations_total.labels(result="valid").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7d92eeb729c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 402 == 401
E        +  where 402 = <Response [402 Payment Required]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Invalid webhook signature", "client_ip": "testclient", "logger": "stampbot.main", "level": "warning", "timestamp": "2026-11-04T04:38:42.272902Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 402 Payment Required\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:38:42.273597Z"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.main:main.py:512 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'warning', 'timestamp': '2026-11-04T04:38:42.272902Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 402 Payment Required"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 402 == 401
 +  where 402 = <Response [402 Payment Required]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.12s
operator: core/NumberReplacer, occurrence: 35, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -510,7 +510,7 @@
         webhook_signature_validations_total.labels(result="invalid").inc()
         errors_total.labels(error_type="signature_invalid").inc()
         logger.warning("Invalid webhook signature")
-        raise HTTPException(status_code=401, detail="Invalid signature")
+        raise HTTPException(status_code= 400, detail="Invalid signature")
 
     webhook_signature_validations_total.labels(result="valid").inc()
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7ef6d845a9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 400 == 401
E        +  where 400 = <Response [400 Bad Request]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Invalid webhook signature", "client_ip": "testclient", "logger": "stampbot.main", "level": "warning", "timestamp": "2026-11-04T03:09:20.265717Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "warning"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 400 Bad Request\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:09:20.266359Z"}
------------------------------ Captured log call -------------------------------
WARNING  stampbot.main:main.py:512 {'event': 'Invalid webhook signature', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'warning', 'timestamp': '2026-11-04T03:09:20.265717Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 400 Bad Request"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 400 == 401
 +  where 400 = <Response [400 Bad Request]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.09s
operator: core/NumberReplacer, occurrence: 36, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -521,7 +521,7 @@
     except Exception as e:
         errors_total.labels(error_type="payload_invalid").inc()
         logger.error("Failed to parse webhook payload: %s", e)
-        raise HTTPException(status_code=400, detail="Invalid JSON payload") from None
+        raise HTTPException(status_code= 401, detail="Invalid JSON payload") from None
 
     # The header is only trusted once the HMAC check has passed. Bound length
     # keeps a malformed value out of log storage.
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.............F
=================================== FAILURES ===================================
__________________________ test_webhook_invalid_json ___________________________

test_client = <starlette.testclient.TestClient object at 0x78925146fce0>

    def test_webhook_invalid_json(test_client: TestClient):
        """Test webhook rejects invalid JSON payload."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        # Create a valid signature for invalid JSON
        body = b"not valid json"
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
>       assert response.status_code == 400
E       assert 401 == 400
E        +  where 401 = <Response [401 Unauthorized]>.status_code

tests/test_main.py:335: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:25:34.785729Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 401 Unauthorized\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:25:34.786233Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:523 {'event': 'Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:25:34.785729Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 401 Unauthorized"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_invalid_json - assert 401 == 400
 +  where 401 = <Response [401 Unauthorized]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 229 passed, 3 deselected, 1 warning in 2.15s
operator: core/NumberReplacer, occurrence: 37, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -521,7 +521,7 @@
     except Exception as e:
         errors_total.labels(error_type="payload_invalid").inc()
         logger.error("Failed to parse webhook payload: %s", e)
-        raise HTTPException(status_code=400, detail="Invalid JSON payload") from None
+        raise HTTPException(status_code= 399, detail="Invalid JSON payload") from None
 
     # The header is only trusted once the HMAC check has passed. Bound length
     # keeps a malformed value out of log storage.
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.............F
=================================== FAILURES ===================================
__________________________ test_webhook_invalid_json ___________________________

test_client = <starlette.testclient.TestClient object at 0x76a513983ce0>

    def test_webhook_invalid_json(test_client: TestClient):
        """Test webhook rejects invalid JSON payload."""
        import hashlib
        import hmac
    
        from stampbot.webhook_handler import webhook_handler
    
        # Create a valid signature for invalid JSON
        body = b"not valid json"
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        response = test_client.post(
            "/webhook",
            content=body,
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": signature,
            },
        )
>       assert response.status_code == 400
E       assert 399 == 400
E        +  where 399 = <Response [399 ]>.status_code

tests/test_main.py:335: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:33:04.967668Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 399 \"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:33:04.968218Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:523 {'event': 'Failed to parse webhook payload: Expecting value: line 1 column 1 (char 0)', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:33:04.967668Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 399 "
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_invalid_json - assert 399 == 400
 +  where 399 = <Response [399 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 229 passed, 3 deselected, 1 warning in 2.13s
operator: core/NumberReplacer, occurrence: 38, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -546,7 +546,7 @@
         errors_total.labels(error_type="webhook_handler_error").inc()
         sanitized = _sanitize_error(e)
         logger.error("Error handling webhook event: %s", sanitized, extra={"error": sanitized})
-        raise HTTPException(status_code=500, detail="Internal server error") from None
+        raise HTTPException(status_code= 501, detail="Internal server error") from None
 
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........................F
=================================== FAILURES ===================================
________________________ test_webhook_handler_exception ________________________

test_client = <starlette.testclient.TestClient object at 0x77a0099a7570>

    def test_webhook_handler_exception(test_client: TestClient):
        """Test webhook returns 500 when handler raises exception."""
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "test"}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        with patch("stampbot.main.webhook_handler.handle_event") as mock_handle:
            mock_handle.side_effect = Exception("Unexpected error")
    
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-Hub-Signature-256": signature,
                },
            )
    
>       assert response.status_code == 500
E       assert 501 == 500
E        +  where 501 = <Response [501 Not Implemented]>.status_code

tests/test_main.py:658: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"error": "Unexpected error"}, "event": "Error handling webhook event: Unexpected error", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:08:57.544926Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 501 Not Implemented\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:08:57.545434Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:548 {'extra': {'error': 'Unexpected error'}, 'event': 'Error handling webhook event: Unexpected error', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:08:57.544926Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 501 Not Implemented"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_handler_exception - assert 501 == 500
 +  where 501 = <Response [501 Not Implemented]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 241 passed, 3 deselected, 1 warning in 2.20s
operator: core/NumberReplacer, occurrence: 39, definition_name: webhook
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -546,7 +546,7 @@
         errors_total.labels(error_type="webhook_handler_error").inc()
         sanitized = _sanitize_error(e)
         logger.error("Error handling webhook event: %s", sanitized, extra={"error": sanitized})
-        raise HTTPException(status_code=500, detail="Internal server error") from None
+        raise HTTPException(status_code= 499, detail="Internal server error") from None
 
 
 # =============================================================================
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........................F
=================================== FAILURES ===================================
________________________ test_webhook_handler_exception ________________________

test_client = <starlette.testclient.TestClient object at 0x78ba7b187570>

    def test_webhook_handler_exception(test_client: TestClient):
        """Test webhook returns 500 when handler raises exception."""
        from stampbot.webhook_handler import webhook_handler
    
        body = json.dumps({"zen": "test"}).encode()
        signature = (
            "sha256="
            + hmac.new(
                webhook_handler.webhook_secret,
                body,
                hashlib.sha256,
            ).hexdigest()
        )
    
        with patch("stampbot.main.webhook_handler.handle_event") as mock_handle:
            mock_handle.side_effect = Exception("Unexpected error")
    
            response = test_client.post(
                "/webhook",
                content=body,
                headers={
                    "Content-Type": "application/json",
                    "X-GitHub-Event": "ping",
                    "X-Hub-Signature-256": signature,
                },
            )
    
>       assert response.status_code == 500
E       assert 499 == 500
E        +  where 499 = <Response [499 ]>.status_code

tests/test_main.py:658: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"error": "Unexpected error"}, "event": "Error handling webhook event: Unexpected error", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T03:34:13.251621Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 499 \"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:34:13.252184Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:548 {'extra': {'error': 'Unexpected error'}, 'event': 'Error handling webhook event: Unexpected error', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T03:34:13.251621Z'}
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 499 "
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_webhook_handler_exception - assert 499 == 500
 +  where 499 = <Response [499 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 241 passed, 3 deselected, 1 warning in 2.17s
operator: core/NumberReplacer, occurrence: 40, definition_name: setup_callback
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -652,7 +652,7 @@
         credentials = await exchange_code_for_credentials(code)
     except Exception as e:
         logger.error("Failed to exchange code for credentials: %s", _sanitize_error(e))
-        raise HTTPException(status_code=500, detail="Failed to complete setup") from None
+        raise HTTPException(status_code= 501, detail="Failed to complete setup") from None
 
     # Security note: Credentials are displayed in the HTML response for user convenience.
     # This mirrors GitHub's own manifest flow behavior. The tradeoffs are acceptable because:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.......................................F
=================================== FAILURES ===================================
____________ TestSetupCallback.test_callback_handles_exchange_error ____________

self = <tests.test_setup_endpoints.TestSetupCallback object at 0x793071f603e0>
test_client = <starlette.testclient.TestClient object at 0x793073357f00>

    def test_callback_handles_exchange_error(self, test_client):
        """Test callback handles exchange error gracefully."""
        with (
            patch("stampbot.main._require_setup_access"),
            patch(
                "stampbot.main.exchange_code_for_credentials",
                new_callable=AsyncMock,
                side_effect=Exception("API error"),
            ),
        ):
            response = test_client.get("/setup/callback?code=bad-code")
    
>           assert response.status_code == 500
E           assert 501 == 500
E            +  where 501 = <Response [501 Not Implemented]>.status_code

tests/test_setup_endpoints.py:406: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Failed to exchange code for credentials: API error", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T03:11:43.531286Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: GET http://testserver/setup/callback?code=bad-code \"HTTP/1.1 501 Not Implemented\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:11:43.531798Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:654 {'event': 'Failed to exchange code for credentials: API error', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T03:11:43.531286Z'}
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback?code=bad-code "HTTP/1.1 501 Not Implemented"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupCallback::test_callback_handles_exchange_error - assert 501 == 500
 +  where 501 = <Response [501 Not Implemented]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 327 passed, 3 deselected, 1 warning in 2.34s
operator: core/NumberReplacer, occurrence: 41, definition_name: setup_callback
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -652,7 +652,7 @@
         credentials = await exchange_code_for_credentials(code)
     except Exception as e:
         logger.error("Failed to exchange code for credentials: %s", _sanitize_error(e))
-        raise HTTPException(status_code=500, detail="Failed to complete setup") from None
+        raise HTTPException(status_code= 499, detail="Failed to complete setup") from None
 
     # Security note: Credentials are displayed in the HTML response for user convenience.
     # This mirrors GitHub's own manifest flow behavior. The tradeoffs are acceptable because:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
.......................................F
=================================== FAILURES ===================================
____________ TestSetupCallback.test_callback_handles_exchange_error ____________

self = <tests.test_setup_endpoints.TestSetupCallback object at 0x717f06f983e0>
test_client = <starlette.testclient.TestClient object at 0x717f068e7f00>

    def test_callback_handles_exchange_error(self, test_client):
        """Test callback handles exchange error gracefully."""
        with (
            patch("stampbot.main._require_setup_access"),
            patch(
                "stampbot.main.exchange_code_for_credentials",
                new_callable=AsyncMock,
                side_effect=Exception("API error"),
            ),
        ):
            response = test_client.get("/setup/callback?code=bad-code")
    
>           assert response.status_code == 500
E           assert 499 == 500
E            +  where 499 = <Response [499 ]>.status_code

tests/test_setup_endpoints.py:406: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "Failed to exchange code for credentials: API error", "client_ip": "testclient", "logger": "stampbot.main", "level": "error", "timestamp": "2026-11-04T04:18:22.434938Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "error"}
{"event": "HTTP Request: GET http://testserver/setup/callback?code=bad-code \"HTTP/1.1 499 \"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:18:22.435512Z"}
------------------------------ Captured log call -------------------------------
ERROR    stampbot.main:main.py:654 {'event': 'Failed to exchange code for credentials: API error', 'client_ip': 'testclient', 'logger': 'stampbot.main', 'level': 'error', 'timestamp': '2026-11-04T04:18:22.434938Z'}
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback?code=bad-code "HTTP/1.1 499 "
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupCallback::test_callback_handles_exchange_error - assert 499 == 500
 +  where 499 = <Response [499 ]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 327 passed, 3 deselected, 1 warning in 2.38s
operator: core/RemoveDecorator, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -119,9 +119,6 @@
             status_code=503,
             detail=("Setup requires STAMPBOT_BASE_URL to be set to Stampbot's trusted public URL"),
         ) from None
-
-
-@asynccontextmanager
 async def lifespan(app: FastAPI) -> AsyncIterator[None]:
     """Application lifespan manager for startup and shutdown events.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.41s
operator: core/RemoveDecorator, occurrence: 1, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -226,9 +226,6 @@
             partial_match = route_path
 
     return partial_match or UNMATCHED_ENDPOINT
-
-
-@app.middleware("http")
 async def metrics_middleware(request: Request, call_next: Any) -> Response:
     """Middleware to track HTTP metrics.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
............................................F
=================================== FAILURES ===================================
__________________ test_unknown_paths_share_one_bounded_label __________________

test_client = <starlette.testclient.TestClient object at 0x729d0b5f9fd0>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='126018826250496'>, 'http_request_duration_seconds': ...='126018826251168'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='126018826251504'>, ...}

    def test_unknown_paths_share_one_bounded_label(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Arbitrary 404 paths must not create attacker-controlled label values."""
        unknown_paths = (
            "/not-a-route/attacker-value-one",
            "/another/made-up/path/attacker-value-two",
        )
    
        for path in unknown_paths:
            response = test_client.post(path, content=b"request body")
            assert response.status_code == 404
    
>       assert _endpoint_labels(http_metric_spies["http_requests_total"]) == [
            UNMATCHED_ENDPOINT,
            UNMATCHED_ENDPOINT,
        ]
E       AssertionError: assert [] == ['unmatched', 'unmatched']
E         
E         Right contains 2 more items, first extra item: 'unmatched'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + []
E         - [
E         -     'unmatched',
E         -     'unmatched',
E         - ]

tests/test_http_metrics.py:101: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:44:05.360105Z [info     ] HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found" [httpx]
2026-11-04T03:44:05.362146Z [info     ] HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/not-a-route/attacker-value-one "HTTP/1.1 404 Not Found"
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/another/made-up/path/attacker-value-two "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_unknown_paths_share_one_bounded_label - AssertionError: assert [] == ['unmatched', 'unmatched']
  
  Right contains 2 more items, first extra item: 'unmatched'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     'unmatched',
  -     'unmatched',
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 188 passed, 3 deselected, 1 warning in 1.49s
operator: core/RemoveDecorator, occurrence: 2, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -337,9 +337,6 @@
 
 
 app.add_middleware(LoggingMiddleware)
-
-
-@app.get("/")
 async def root() -> Response:
     """Root endpoint - redirects to setup if not configured.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
....................................................................F
=================================== FAILURES ===================================
______________________________ test_root_endpoint ______________________________

test_client = <starlette.testclient.TestClient object at 0x7010aaa84c00>

    def test_root_endpoint(test_client: TestClient):
        """Test root endpoint returns basic info."""
        from stampbot.version import APP_VERSION
    
        response = test_client.get("/")
>       assert response.status_code == 200
E       assert 404 == 200
E        +  where 404 = <Response [404 Not Found]>.status_code

tests/test_main.py:78: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ \"HTTP/1.1 404 Not Found\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:44:02.734809Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_root_endpoint - assert 404 == 200
 +  where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 212 passed, 3 deselected, 1 warning in 1.57s
operator: core/RemoveDecorator, occurrence: 3, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -353,9 +353,6 @@
     return JSONResponse(
         content={"app": "stampbot", "version": APP_VERSION, "status": "running"},
     )
-
-
-@app.get("/health")
 async def health() -> dict[str, str]:
     """Liveness check endpoint.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.................................................................F
=================================== FAILURES ===================================
__________________ test_lifespan_startup_shutdown_configured ___________________

    def test_lifespan_startup_shutdown_configured():
        """Test app lifespan startup and shutdown when configured."""
        from stampbot.main import app
    
        # Use TestClient as context manager to trigger lifespan events
        with TestClient(app) as client:
            # App should be running
            response = client.get("/health")
>           assert response.status_code == 200
E           assert 404 == 200
E            +  where 404 = <Response [404 Not Found]>.status_code

tests/test_main.py:29: AssertionError
----------------------------- Captured stdout call -----------------------------
{"extra": {"host": "0.0.0.0", "port": 8000, "log_level": "INFO"}, "event": "Starting stampbot on 0.0.0.0:8000", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:15:22.757952Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "GitHub App credentials configured successfully", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:15:22.758080Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
{"event": "HTTP Request: GET http://testserver/health \"HTTP/1.1 404 Not Found\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:15:22.759446Z"}
{"event": "Shutting down stampbot", "logger": "stampbot.main", "level": "info", "timestamp": "2026-11-04T03:15:22.760390Z", "_logger": "<_FixedFindCallerLogger stampbot.main (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.main:main.py:137 {'extra': {'host': '0.0.0.0', 'port': 8000, 'log_level': 'INFO'}, 'event': 'Starting stampbot on 0.0.0.0:8000', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:15:22.757952Z'}
INFO     stampbot.main:main.py:155 {'event': 'GitHub App credentials configured successfully', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:15:22.758080Z'}
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/health "HTTP/1.1 404 Not Found"
INFO     stampbot.main:main.py:178 {'event': 'Shutting down stampbot', 'logger': 'stampbot.main', 'level': 'info', 'timestamp': '2026-11-04T03:15:22.760390Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_lifespan_startup_shutdown_configured - assert 404 == 200
 +  where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 209 passed, 3 deselected, 1 warning in 1.56s
operator: core/RemoveDecorator, occurrence: 4, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -368,9 +368,6 @@
         Dictionary with health status.
     """
     return {"status": "healthy"}
-
-
-@app.get("/ready")
 async def ready() -> Response:
     """Readiness check endpoint.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
.......................................................................F [ 45%]

=================================== FAILURES ===================================
________________________ test_ready_endpoint_configured ________________________

test_client = <starlette.testclient.TestClient object at 0x738459f99370>

    def test_ready_endpoint_configured(test_client: TestClient):
        """Test readiness endpoint returns 200 when the app is configured."""
        with patch("stampbot.main.is_configured", return_value=True):
            response = test_client.get("/ready")
>       assert response.status_code == 200
E       assert 404 == 200
E        +  where 404 = <Response [404 Not Found]>.status_code

tests/test_main.py:107: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/ready \"HTTP/1.1 404 Not Found\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:15:10.586534Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/ready "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_ready_endpoint_configured - assert 404 == 200
 +  where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 215 passed, 3 deselected, 1 warning in 1.57s
operator: core/RemoveDecorator, occurrence: 5, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -458,9 +458,6 @@
             raise HTTPException(status_code=413, detail="Request body too large")
         body += chunk
     return bytes(body)
-
-
-@app.post("/webhook")
 async def webhook(
     request: Request,
     x_github_event: str = Header(None, alias="X-GitHub-Event"),
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x7b9b07d7e690>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
        response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )
        # Will fail signature validation, but Content-Length tracking happens before that
>       assert response.status_code == 401
E       assert 404 == 401
E        +  where 404 = <Response [404 Not Found]>.status_code

tests/test_main.py:269: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 404 Not Found\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:17:59.220264Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - assert 404 == 401
 +  where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.11s
operator: core/RemoveDecorator, occurrence: 6, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -547,14 +547,6 @@
         sanitized = _sanitize_error(e)
         logger.error("Error handling webhook event: %s", sanitized, extra={"error": sanitized})
         raise HTTPException(status_code=500, detail="Internal server error") from None
-
-
-# =============================================================================
-# Setup Endpoints - GitHub App Manifest Flow
-# =============================================================================
-
-
-@app.get("/setup")
 async def setup_page() -> Response:
     """Setup page with manifest creation button.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
...............................................F
=================================== FAILURES ===================================
________________ test_setup_routes_keep_distinct_static_labels _________________

test_client = <starlette.testclient.TestClient object at 0x76600b236520>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='130154874259504'>, 'http_request_duration_seconds': ...='130154874260176'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='130154874260512'>, ...}

    def test_setup_routes_keep_distinct_static_labels(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Setup and callback traffic remains distinguishable by route."""
        setup_response = test_client.get("/setup")
        callback_response = test_client.get("/setup/callback")
    
>       assert setup_response.status_code == 403
E       assert 404 == 403
E        +  where 404 = <Response [404 Not Found]>.status_code

tests/test_http_metrics.py:143: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:22:38.170391Z [info     ] HTTP Request: GET http://testserver/setup "HTTP/1.1 404 Not Found" [httpx]
2026-11-04T03:22:38.173467Z [info     ] HTTP Request: GET http://testserver/setup/callback "HTTP/1.1 422 Unprocessable Entity" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 404 Not Found"
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback "HTTP/1.1 422 Unprocessable Entity"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_setup_routes_keep_distinct_static_labels - assert 404 == 403
 +  where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 191 passed, 3 deselected, 1 warning in 1.53s
operator: core/RemoveDecorator, occurrence: 7, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -631,9 +631,6 @@
     """
 
     return HTMLResponse(content=html_content, headers=SETUP_HTML_HEADERS)
-
-
-@app.get("/setup/callback")
 async def setup_callback(code: str) -> Response:
     """Handle callback from GitHub after app creation.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
...............................................F
=================================== FAILURES ===================================
________________ test_setup_routes_keep_distinct_static_labels _________________

test_client = <starlette.testclient.TestClient object at 0x7b2276f2a520>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='135387953186864'>, 'http_request_duration_seconds': ...='135387953187536'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='135387953187872'>, ...}

    def test_setup_routes_keep_distinct_static_labels(
        test_client: TestClient,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Setup and callback traffic remains distinguishable by route."""
        setup_response = test_client.get("/setup")
        callback_response = test_client.get("/setup/callback")
    
        assert setup_response.status_code == 403
>       assert callback_response.status_code == 422
E       assert 404 == 422
E        +  where 404 = <Response [404 Not Found]>.status_code

tests/test_http_metrics.py:144: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T03:58:29.787130Z [info     ] HTTP Request: GET http://testserver/setup "HTTP/1.1 403 Forbidden" [httpx]
2026-11-04T03:58:29.789602Z [info     ] HTTP Request: GET http://testserver/setup/callback "HTTP/1.1 404 Not Found" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup "HTTP/1.1 403 Forbidden"
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/callback "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_setup_routes_keep_distinct_static_labels - assert 404 == 422
 +  where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 191 passed, 3 deselected, 1 warning in 1.49s
operator: core/RemoveDecorator, occurrence: 8, definition_name: None
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -749,9 +749,6 @@
     """
 
     return HTMLResponse(content=html_content, headers=SETUP_HTML_HEADERS)
-
-
-@app.get("/setup/status")
 async def setup_status() -> dict[str, Any]:
     """Check setup status.
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
............................F
=================================== FAILURES ===================================
_ TestSetupEndpointsConfigured.test_setup_status_is_closed_and_does_not_disclose_app_id _

self = <tests.test_setup_endpoints.TestSetupEndpointsConfigured object at 0x7b580270c050>

    def test_setup_status_is_closed_and_does_not_disclose_app_id(self):
        """Test setup status follows the configured-state gate."""
        with (
            patch("stampbot.main.is_configured", return_value=True),
            patch("stampbot.main.settings") as mock_settings,
        ):
            configure_setup_settings(mock_settings, enabled=True)
    
            from fastapi.testclient import TestClient
    
            from stampbot.main import app
    
            response = TestClient(app, raise_server_exceptions=False).get("/setup/status")
    
>       assert response.status_code == 403
E       assert 404 == 403
E        +  where 404 = <Response [404 Not Found]>.status_code

tests/test_setup_endpoints.py:138: AssertionError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: GET http://testserver/setup/status \"HTTP/1.1 404 Not Found\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:30:35.093903Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/setup/status "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_setup_endpoints.py::TestSetupEndpointsConfigured::test_setup_status_is_closed_and_does_not_disclose_app_id - assert 404 == 403
 +  where 404 = <Response [404 Not Found]>.status_code
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 316 passed, 3 deselected, 1 warning in 2.33s
operator: core/ZeroIterationForLoop, occurrence: 0, definition_name: _metric_endpoint_label
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -213,7 +213,7 @@
         return current_path
 
     partial_match: str | None = None
-    for route in request.app.routes:
+    for route in []:
         match, child_scope = route.matches(request.scope)
         matched_route = child_scope.get("route", route)
         route_path = getattr(matched_route, "path", None)
........................................................................ [ 15%]
........................................................................ [ 30%]
..............................................F
=================================== FAILURES ===================================
____________________ test_dynamic_path_uses_route_template _____________________

routed_app = <fastapi.applications.FastAPI object at 0x7abcbd3e7230>
http_metric_spies = {'http_requests_total': <MagicMock name='http_requests_total' id='134951047426272'>, 'http_request_duration_seconds': ...='134951047429632'>, 'http_response_size_bytes': <MagicMock name='http_response_size_bytes' id='134951045608656'>, ...}

    def test_dynamic_path_uses_route_template(
        routed_app: FastAPI,
        http_metric_spies: dict[str, MagicMock],
    ) -> None:
        """Path parameters are represented by their stable route template."""
        client = TestClient(routed_app)
    
        response = client.get("/widgets/user-controlled-123")
    
        assert response.status_code == 200
>       assert _endpoint_labels(http_metric_spies["http_requests_total"]) == ["/widgets/{widget_id}"]
E       AssertionError: assert ['unmatched'] == ['/widgets/{widget_id}']
E         
E         At index 0 diff: 'unmatched' != '/widgets/{widget_id}'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E           [
E         -     '/widgets/{widget_id}',
E         +     'unmatched',
E           ]

tests/test_http_metrics.py:130: AssertionError
----------------------------- Captured stdout call -----------------------------
2026-11-04T04:06:22.942221Z [info     ] HTTP Request: GET http://testserver/widgets/user-controlled-123 "HTTP/1.1 200 OK" [httpx]
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: GET http://testserver/widgets/user-controlled-123 "HTTP/1.1 200 OK"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_http_metrics.py::test_dynamic_path_uses_route_template - AssertionError: assert ['unmatched'] == ['/widgets/{widget_id}']
  
  At index 0 diff: 'unmatched' != '/widgets/{widget_id}'
  
  Full diff: (-: missing in left side, +: extra in left side)
    [
  -     '/widgets/{widget_id}',
  +     'unmatched',
    ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 190 passed, 3 deselected, 1 warning in 1.49s
operator: core/ZeroIterationForLoop, occurrence: 1, definition_name: _read_body_within_limit
--- mutation diff ---
--- a/stampbot/main.py
+++ b/stampbot/main.py
@@ -452,7 +452,7 @@
         Everything downstream must use the returned bytes.
     """
     body = bytearray()
-    async for chunk in request.stream():
+    async for chunk in []:
         if len(body) + len(chunk) > MAX_WEBHOOK_BODY_SIZE:
             errors_total.labels(error_type="payload_too_large").inc()
             raise HTTPException(status_code=413, detail="Request body too large")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........F
=================================== FAILURES ===================================
_______________________ test_request_with_content_length _______________________

test_client = <starlette.testclient.TestClient object at 0x77762d15a9c0>

    def test_request_with_content_length(test_client: TestClient):
        """Test that requests with Content-Length header are tracked."""
        # POST with a body will have Content-Length set automatically
>       response = test_client.post(
            "/webhook",
            content=b'{"test": "data"}',
            headers={
                "Content-Type": "application/json",
                "X-GitHub-Event": "ping",
                "X-Hub-Signature-256": "sha256=invalid",
            },
        )

tests/test_main.py:259: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:1144: in post
    return self.request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:518: in request
    return super().request(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:825: in request
    return self.send(request, auth=auth, follow_redirects=follow_redirects)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:928: in send
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:922: in send
    response.read()
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:881: in read
    self._content = b"".join(self.iter_bytes())
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:897: in iter_bytes
    for raw_bytes in self.iter_raw():
                     ^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:951: in iter_raw
    for raw_stream_bytes in self.stream:
                            ^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_client.py:153: in __iter__
    for chunk in self._stream:
                 ^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:384: in iter_body
    exit_stack.close()
/usr/lib/python3.14/contextlib.py:627: in close
    self.__exit__(None, None, None)
/usr/lib/python3.14/contextlib.py:619: in __exit__
    raise exc
/usr/lib/python3.14/contextlib.py:604: in __exit__
    if cb(*exc_details):
       ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:399: in close_app
    raise exception
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/from_thread.py:272: in _call_func
    retval = await retval_or_awaitable
             ^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/testclient.py:366: in run_app
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:1194: in __call__
    await super().__call__(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/applications.py:96: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:186: in __call__
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/errors.py:164: in __call__
    await self.app(scope, receive, _send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/telemetry/_asgi.py:151: in __call__
    await self.app(scope, receive, send)
stampbot/main.py:336: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:203: in __call__
    response = await self.dispatch_func(request, call_next)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:251: in metrics_middleware
    response = await call_next(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:179: in call_next
    raise app_exc from app_exc.__cause__ or app_exc.__context__
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/base.py:149: in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/middleware/exceptions.py:63: in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py:18: in __call__
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:683: in __call__
    await self.middleware_stack(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:2811: in app
    await route.handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:1330: in handle
    await super().handle(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/routing.py:289: in handle
    await self.app(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:166: in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:53: in wrapped_app
    raise exc
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/_exception_handler.py:42: in wrapped_app
    await app(scope, receive, sender)
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:152: in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:745: in app
    raw_response = await run_endpoint_function(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/routing.py:372: in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/main.py:506: in webhook
    body = await _read_body_within_limit(request)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

request = <starlette.requests.Request object at 0x777627e74c00>

    async def _read_body_within_limit(request: Request) -> bytes:
        """Read the request body, stopping as soon as it passes the size limit.
    
        ``Request.body()`` buffers the whole stream before anything can measure it.
        A chunked request carries no ``Content-Length``, so the pre-read check
        cannot see its size, and an unauthenticated client could make the process
        hold an arbitrarily large body in memory before the limit applied. Counting
        bytes as they arrive bounds that to ``MAX_WEBHOOK_BODY_SIZE`` plus the chunk
        that crosses it.
    
        The bytes accumulate in one contiguous buffer rather than a list of chunks.
        The client also controls chunk size, and a list would charge a separate
        object header and pointer per chunk: a body paced in two-byte chunks would
        cost about twenty times its length before the limit noticed anything.
    
        Args:
            request: The incoming request, whose stream has not been consumed.
    
        Returns:
            The complete body.
    
        Raises:
            HTTPException: 413 once the bytes read exceed ``MAX_WEBHOOK_BODY_SIZE``.
    
        Note:
            This consumes the stream without caching it on the request, so a later
            ``request.body()`` or ``request.json()`` raises ``RuntimeError``.
            Everything downstream must use the returned bytes.
        """
        body = bytearray()
>       async for chunk in []:
                           ^^
E       TypeError: 'async for' requires an object with __aiter__ method, got list

stampbot/main.py:455: TypeError
----------------------------- Captured stdout call -----------------------------
{"event": "HTTP Request: POST http://testserver/webhook \"HTTP/1.1 500 Internal Server Error\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T03:33:26.096673Z"}
------------------------------ Captured log call -------------------------------
INFO     httpx:_client.py:1025 HTTP Request: POST http://testserver/webhook "HTTP/1.1 500 Internal Server Error"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_main.py::test_request_with_content_length - TypeError: 'async for' requires an object with __aiter__ method, got list
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 225 passed, 3 deselected, 1 warning in 2.22s
operator: core/AddNot, occurrence: 0, definition_name: _resolve_app_version
--- mutation diff ---
--- a/stampbot/version.py
+++ b/stampbot/version.py
@@ -15,7 +15,7 @@
 def _resolve_app_version() -> str:
     """Return build-injected, installed-package, or unknown version metadata."""
     injected_version = os.getenv(_VERSION_ENVIRONMENT_VARIABLE, "").strip()
-    if injected_version:
+    if not injected_version:
         return injected_version
 
     try:
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:182: in <module>
    app = FastAPI(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/applications.py:925: in __init__
    assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'"
           ^^^^^^^^^^^^
E   AssertionError: A version must be provided for OpenAPI, e.g.: '2.1.0'
------------------------------- Captured stdout --------------------------------
2026-11-04T04:39:42.979677Z [info     ] OpenTelemetry disabled         [stampbot.telemetry] _logger=<_FixedFindCallerLogger stampbot.telemetry (INFO)> _name=info
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - AssertionError: A version must be provided for OpenAPI, e.g.: '2.1.0'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.00s
operator: core/ReplaceOrWithAnd, occurrence: 0, definition_name: _resolve_app_version
--- mutation diff ---
--- a/stampbot/version.py
+++ b/stampbot/version.py
@@ -23,7 +23,7 @@
     except PackageNotFoundError:
         return _UNKNOWN_VERSION
 
-    return installed_version or _UNKNOWN_VERSION
+    return installed_version and _UNKNOWN_VERSION
 
 
 APP_VERSION = _resolve_app_version()
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
...................................................................F
=================================== FAILURES ===================================
_____________ test_installed_distribution_is_development_fallback ______________

monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7b049dc10e50>

    def test_installed_distribution_is_development_fallback(monkeypatch):
        monkeypatch.delenv("STAMPBOT_VERSION", raising=False)
    
        with patch("stampbot.version.distribution_version", return_value="1.2.3"):
>           assert _resolve_app_version() == "1.2.3"
E           AssertionError: assert '0.0.0+unknown' == '1.2.3'
E             
E             - 1.2.3
E             + 0.0.0+unknown

tests/test_version.py:22: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_version.py::test_installed_distribution_is_development_fallback - AssertionError: assert '0.0.0+unknown' == '1.2.3'
  
  - 1.2.3
  + 0.0.0+unknown
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 355 passed, 3 deselected, 1 warning in 2.44s
operator: core/ExceptionReplacer, occurrence: 0, definition_name: _resolve_app_version
--- mutation diff ---
--- a/stampbot/version.py
+++ b/stampbot/version.py
@@ -20,7 +20,7 @@
 
     try:
         installed_version = distribution_version(_DISTRIBUTION_NAME).strip()
-    except PackageNotFoundError:
+    except CosmicRayTestingException:
         return _UNKNOWN_VERSION
 
     return installed_version or _UNKNOWN_VERSION
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
....................................................................F
=================================== FAILURES ===================================
_________ test_unknown_fallback_without_build_or_distribution_version __________

    def _resolve_app_version() -> str:
        """Return build-injected, installed-package, or unknown version metadata."""
        injected_version = os.getenv(_VERSION_ENVIRONMENT_VARIABLE, "").strip()
        if injected_version:
            return injected_version
    
        try:
>           installed_version = distribution_version(_DISTRIBUTION_NAME).strip()
                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/version.py:22: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='distribution_version' id='126252227068992'>
args = ('stampbot',), kwargs = {}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               importlib.metadata.PackageNotFoundError: <exception str() failed>

/usr/lib/python3.14/unittest/mock.py:1241: PackageNotFoundError

During handling of the above exception, another exception occurred:

monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x72d361e05630>

    def test_unknown_fallback_without_build_or_distribution_version(monkeypatch):
        monkeypatch.setenv("STAMPBOT_VERSION", "   ")
    
        with patch(
            "stampbot.version.distribution_version",
            side_effect=PackageNotFoundError,
        ):
>           assert _resolve_app_version() == "0.0.0+unknown"
                   ^^^^^^^^^^^^^^^^^^^^^^

tests/test_version.py:32: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def _resolve_app_version() -> str:
        """Return build-injected, installed-package, or unknown version metadata."""
        injected_version = os.getenv(_VERSION_ENVIRONMENT_VARIABLE, "").strip()
        if injected_version:
            return injected_version
    
        try:
            installed_version = distribution_version(_DISTRIBUTION_NAME).strip()
>       except CosmicRayTestingException:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/version.py:23: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_version.py::test_unknown_fallback_without_build_or_distribution_version - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 356 passed, 3 deselected, 1 warning in 2.41s
operator: core/ReplaceBinaryOperator_Mul_Add, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS + 1000)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
        is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )
    
        assert is_eligible is False
>       assert f"{int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)} ms safety limit" in reason
E       AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 1000 ms safety limit'

tests/test_config.py:450: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 1000 ms safety limit'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.35s
operator: core/ReplaceBinaryOperator_Mul_Sub, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS - 1000)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
        is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )
    
        assert is_eligible is False
>       assert f"{int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)} ms safety limit" in reason
E       AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the -999 ms safety limit'

tests/test_config.py:450: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the -999 ms safety limit'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.38s
operator: core/ReplaceBinaryOperator_Mul_Div, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS / 1000)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
        is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )
    
        assert is_eligible is False
>       assert f"{int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)} ms safety limit" in reason
E       AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 0 ms safety limit'

tests/test_config.py:450: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 0 ms safety limit'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceBinaryOperator_Mul_FloorDiv, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS // 1000)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
        is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )
    
        assert is_eligible is False
>       assert f"{int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)} ms safety limit" in reason
E       AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 0 ms safety limit'

tests/test_config.py:450: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 0 ms safety limit'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceBinaryOperator_Mul_Mod, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS % 1000)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
        is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )
    
        assert is_eligible is False
>       assert f"{int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)} ms safety limit" in reason
E       AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 0 ms safety limit'

tests/test_config.py:450: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 0 ms safety limit'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceBinaryOperator_Mul_Pow, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS ** 1000)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
        is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )
    
        assert is_eligible is False
>       assert f"{int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)} ms safety limit" in reason
E       AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 0 ms safety limit'

tests/test_config.py:450: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 0 ms safety limit'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.35s
operator: core/ReplaceBinaryOperator_Mul_RShift, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS >> 1000)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

self = <stampbot.config.RepoConfig object at 0x73806f300ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
>                   if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
E                   TimeoutError: regex timed out

stampbot/config.py:320: TimeoutError

During handling of the above exception, another exception occurred:

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
>       is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )

tests/test_config.py:445: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.config.RepoConfig object at 0x73806f300ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
                    if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
                        break
                except TimeoutError:
>                   timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS >> 1000)
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E                   TypeError: unsupported operand type(s) for >>: 'float' and 'int'

stampbot/config.py:327: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - TypeError: unsupported operand type(s) for >>: 'float' and 'int'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceBinaryOperator_Mul_LShift, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS << 1000)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

self = <stampbot.config.RepoConfig object at 0x7e4090d54ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
>                   if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
E                   TimeoutError: regex timed out

stampbot/config.py:320: TimeoutError

During handling of the above exception, another exception occurred:

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
>       is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )

tests/test_config.py:445: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.config.RepoConfig object at 0x7e4090d54ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
                    if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
                        break
                except TimeoutError:
>                   timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS << 1000)
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E                   TypeError: unsupported operand type(s) for <<: 'float' and 'int'

stampbot/config.py:327: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - TypeError: unsupported operand type(s) for <<: 'float' and 'int'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.31s
operator: core/ReplaceBinaryOperator_Mul_BitOr, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS | 1000)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

self = <stampbot.config.RepoConfig object at 0x70e2e5d84ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
>                   if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
E                   TimeoutError: regex timed out

stampbot/config.py:320: TimeoutError

During handling of the above exception, another exception occurred:

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
>       is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )

tests/test_config.py:445: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.config.RepoConfig object at 0x70e2e5d84ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
                    if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
                        break
                except TimeoutError:
>                   timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS | 1000)
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E                   TypeError: unsupported operand type(s) for |: 'float' and 'int'

stampbot/config.py:327: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - TypeError: unsupported operand type(s) for |: 'float' and 'int'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.36s
operator: core/ReplaceBinaryOperator_Mul_BitAnd, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS & 1000)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

self = <stampbot.config.RepoConfig object at 0x789e80328ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
>                   if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
E                   TimeoutError: regex timed out

stampbot/config.py:320: TimeoutError

During handling of the above exception, another exception occurred:

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
>       is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )

tests/test_config.py:445: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.config.RepoConfig object at 0x789e80328ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
                    if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
                        break
                except TimeoutError:
>                   timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS & 1000)
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E                   TypeError: unsupported operand type(s) for &: 'float' and 'int'

stampbot/config.py:327: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - TypeError: unsupported operand type(s) for &: 'float' and 'int'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceBinaryOperator_Mul_BitXor, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS ^ 1000)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

self = <stampbot.config.RepoConfig object at 0x72cbff728ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
>                   if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
E                   TimeoutError: regex timed out

stampbot/config.py:320: TimeoutError

During handling of the above exception, another exception occurred:

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
>       is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )

tests/test_config.py:445: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.config.RepoConfig object at 0x72cbff728ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
                    if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
                        break
                except TimeoutError:
>                   timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS ^ 1000)
                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E                   TypeError: unsupported operand type(s) for ^: 'float' and 'int'

stampbot/config.py:327: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - TypeError: unsupported operand type(s) for ^: 'float' and 'int'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.36s
operator: core/ReplaceBinaryOperator_Pow_Add, occurrence: 0, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {+defaults, **repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {+defaults, **repo_settings}
E                            ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_Add, occurrence: 1, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {**defaults, +repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {**defaults, +repo_settings}
E                                          ^
E   SyntaxError: ':' expected after dictionary key
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_Sub, occurrence: 0, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {-defaults, **repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {-defaults, **repo_settings}
E                            ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_Sub, occurrence: 1, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {**defaults, -repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {**defaults, -repo_settings}
E                                          ^
E   SyntaxError: ':' expected after dictionary key
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_Mul, occurrence: 0, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {*defaults, **repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {*defaults, **repo_settings}
E                            ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_Mul, occurrence: 1, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {**defaults, *repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {**defaults, *repo_settings}
E                             ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_Div, occurrence: 0, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {/defaults, **repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {/defaults, **repo_settings}
E                 ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_Div, occurrence: 1, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {**defaults, /repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {**defaults, /repo_settings}
E                             ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_FloorDiv, occurrence: 0, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {//defaults, **repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {//defaults, **repo_settings}
E                 ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_FloorDiv, occurrence: 1, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {**defaults, //repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {**defaults, //repo_settings}
E                             ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_Mod, occurrence: 0, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {%defaults, **repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {%defaults, **repo_settings}
E                 ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_Mod, occurrence: 1, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {**defaults, %repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {**defaults, %repo_settings}
E                             ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_RShift, occurrence: 0, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {>>defaults, **repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {>>defaults, **repo_settings}
E                 ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_RShift, occurrence: 1, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {**defaults, >>repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {**defaults, >>repo_settings}
E                             ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_LShift, occurrence: 0, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {<<defaults, **repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {<<defaults, **repo_settings}
E                 ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_LShift, occurrence: 1, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {**defaults, <<repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {**defaults, <<repo_settings}
E                             ^^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_BitOr, occurrence: 0, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {|defaults, **repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {|defaults, **repo_settings}
E                 ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_BitOr, occurrence: 1, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {**defaults, |repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {**defaults, |repo_settings}
E                             ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_BitAnd, occurrence: 0, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {&defaults, **repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {&defaults, **repo_settings}
E                 ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_BitAnd, occurrence: 1, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {**defaults, &repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {**defaults, &repo_settings}
E                             ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_BitXor, occurrence: 0, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {^defaults, **repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {^defaults, **repo_settings}
E                 ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceBinaryOperator_Pow_BitXor, occurrence: 1, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -422,7 +422,7 @@
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
-        merged = {**defaults, **repo_settings}
+        merged = {**defaults, ^repo_settings}
 
         title_patterns = merged.get("required_title_patterns", [])
 
==================================== ERRORS ====================================
____________________ ERROR collecting tests/test_config.py _____________________
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/python.py:528: in importtestmodule
    mod = import_path(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/pathlib.py:640: in import_path
    importlib.import_module(module_name)
/usr/lib/python3.14/importlib/__init__.py:88: in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
<frozen importlib._bootstrap>:1406: in _gcd_import
    ???
<frozen importlib._bootstrap>:1371: in _find_and_load
    ???
<frozen importlib._bootstrap>:1342: in _find_and_load_unlocked
    ???
<frozen importlib._bootstrap>:938: in _load_unlocked
    ???
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/_pytest/assertion/rewrite.py:185: in exec_module
    exec(co, module.__dict__)
tests/test_config.py:8: in <module>
    from stampbot.config import (
E     File "/home/runner/work/stampbot/stampbot/stampbot/config.py", line 425
E       merged = {**defaults, ^repo_settings}
E                             ^
E   SyntaxError: invalid syntax
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_config.py
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 0.11s
operator: core/ReplaceComparisonOperator_Lt_Eq, occurrence: 0, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -90,7 +90,7 @@
         seconds = int(raw)
     except (TypeError, ValueError) as error:
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
-    if seconds < 0:
+    if seconds == 0:
         raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
     return seconds
 
.......................................................................F [ 15%]

=================================== FAILURES ===================================
_____________ test_repo_config_cache_seconds_accepts_integers[0-0] _____________

raw = 0, expected = 0

    @pytest.mark.parametrize(
        ("raw", "expected"),
        [(300, 300), ("120", 120), (0, 0), (5.0, 5)],
    )
    def test_repo_config_cache_seconds_accepts_integers(raw, expected):
        """Test the cache lifetime accepts integer-valued settings."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = raw
    
>           assert repo_config_cache_seconds() == expected
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:627: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def repo_config_cache_seconds() -> int:
        """Return how long a repository's parsed policy stays cached, in seconds.
    
        Returns:
            A non-negative integer. Zero disables the cache.
    
        Raises:
            ValueError: If ``repo_config_cache_seconds`` is not a non-negative integer.
        """
        raw = settings.get("repo_config_cache_seconds", DEFAULT_REPO_CONFIG_CACHE_SECONDS)
        # Dynaconf parses environment values, so ``false`` arrives as a bool and
        # ``1.9`` as a float. Neither is an integer setting, and int() would accept
        # both silently.
        if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
            raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
        try:
            seconds = int(raw)
        except (TypeError, ValueError) as error:
            raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
        if seconds == 0:
>           raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
E           ValueError: repo_config_cache_seconds must be 0 or greater, got 0

stampbot/config.py:94: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_cache_seconds_accepts_integers[0-0] - ValueError: repo_config_cache_seconds must be 0 or greater, got 0
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 71 passed, 3 deselected, 1 warning in 1.39s
operator: core/ReplaceComparisonOperator_Lt_NotEq, occurrence: 0, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -90,7 +90,7 @@
         seconds = int(raw)
     except (TypeError, ValueError) as error:
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
-    if seconds < 0:
+    if seconds != 0:
         raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
     return seconds
 
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
stampbot/webhook_handler.py:1149: in <module>
    webhook_handler = WebhookHandler()
                      ^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:84: in __init__
    self._policy = RepoPolicyResolver(repo_config_cache_seconds())
                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/config.py:94: in repo_config_cache_seconds
    raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
E   ValueError: repo_config_cache_seconds must be 0 or greater, got 300
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: repo_config_cache_seconds must be 0 or greater, got 300
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.04s
operator: core/ReplaceComparisonOperator_Lt_LtE, occurrence: 0, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -90,7 +90,7 @@
         seconds = int(raw)
     except (TypeError, ValueError) as error:
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
-    if seconds < 0:
+    if seconds <= 0:
         raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
     return seconds
 
.......................................................................F [ 15%]

=================================== FAILURES ===================================
_____________ test_repo_config_cache_seconds_accepts_integers[0-0] _____________

raw = 0, expected = 0

    @pytest.mark.parametrize(
        ("raw", "expected"),
        [(300, 300), ("120", 120), (0, 0), (5.0, 5)],
    )
    def test_repo_config_cache_seconds_accepts_integers(raw, expected):
        """Test the cache lifetime accepts integer-valued settings."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = raw
    
>           assert repo_config_cache_seconds() == expected
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:627: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def repo_config_cache_seconds() -> int:
        """Return how long a repository's parsed policy stays cached, in seconds.
    
        Returns:
            A non-negative integer. Zero disables the cache.
    
        Raises:
            ValueError: If ``repo_config_cache_seconds`` is not a non-negative integer.
        """
        raw = settings.get("repo_config_cache_seconds", DEFAULT_REPO_CONFIG_CACHE_SECONDS)
        # Dynaconf parses environment values, so ``false`` arrives as a bool and
        # ``1.9`` as a float. Neither is an integer setting, and int() would accept
        # both silently.
        if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
            raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
        try:
            seconds = int(raw)
        except (TypeError, ValueError) as error:
            raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
        if seconds <= 0:
>           raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
E           ValueError: repo_config_cache_seconds must be 0 or greater, got 0

stampbot/config.py:94: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_cache_seconds_accepts_integers[0-0] - ValueError: repo_config_cache_seconds must be 0 or greater, got 0
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 71 passed, 3 deselected, 1 warning in 1.36s
operator: core/ReplaceComparisonOperator_Lt_Gt, occurrence: 0, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -90,7 +90,7 @@
         seconds = int(raw)
     except (TypeError, ValueError) as error:
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
-    if seconds < 0:
+    if seconds > 0:
         raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
     return seconds
 
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
stampbot/webhook_handler.py:1149: in <module>
    webhook_handler = WebhookHandler()
                      ^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:84: in __init__
    self._policy = RepoPolicyResolver(repo_config_cache_seconds())
                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/config.py:94: in repo_config_cache_seconds
    raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
E   ValueError: repo_config_cache_seconds must be 0 or greater, got 300
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: repo_config_cache_seconds must be 0 or greater, got 300
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.02s
operator: core/ReplaceComparisonOperator_Lt_GtE, occurrence: 0, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -90,7 +90,7 @@
         seconds = int(raw)
     except (TypeError, ValueError) as error:
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
-    if seconds < 0:
+    if seconds >= 0:
         raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
     return seconds
 
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
stampbot/webhook_handler.py:1149: in <module>
    webhook_handler = WebhookHandler()
                      ^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:84: in __init__
    self._policy = RepoPolicyResolver(repo_config_cache_seconds())
                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/config.py:94: in repo_config_cache_seconds
    raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
E   ValueError: repo_config_cache_seconds must be 0 or greater, got 300
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: repo_config_cache_seconds must be 0 or greater, got 300
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.03s
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 0, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -225,7 +225,7 @@
         """
         if not isinstance(patterns, list):
             raise ValueError("required_title_patterns must be a list of strings")
-        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
+        if len(patterns) == MAX_TITLE_PATTERN_COUNT:
             raise ValueError(
                 "required_title_patterns contains too many patterns "
                 f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
........................................F
=================================== FAILURES ===================================
_______________ test_repo_config_rejects_too_many_title_patterns _______________

    def test_repo_config_rejects_too_many_title_patterns():
        """Test title pattern count is bounded."""
        patterns = ", ".join('"^fix:"' for _ in range(MAX_TITLE_PATTERN_COUNT + 1))
    
>       with pytest.raises(ValueError, match="too many patterns"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_config.py:327: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_rejects_too_many_title_patterns - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 40 passed, 3 deselected, 1 warning in 1.35s
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 1, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -235,7 +235,7 @@
         for pattern in patterns:
             if not isinstance(pattern, str):
                 raise ValueError("required_title_patterns must contain only strings")
-            if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
+            if len(pattern) == MAX_TITLE_PATTERN_LENGTH:
                 raise ValueError(
                     "required_title_patterns contains a pattern longer than "
                     f"{MAX_TITLE_PATTERN_LENGTH} characters"
.........................................F
=================================== FAILURES ===================================
_________________ test_repo_config_rejects_long_title_pattern __________________

    def test_repo_config_rejects_long_title_pattern():
        """Test individual title pattern length is bounded."""
        pattern = "a" * (MAX_TITLE_PATTERN_LENGTH + 1)
    
>       with pytest.raises(ValueError, match="pattern longer than"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_config.py:335: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_rejects_long_title_pattern - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 41 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceComparisonOperator_Gt_Eq, occurrence: 2, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -309,7 +309,7 @@
         if self._compiled_title_patterns:
             if not isinstance(pr_title, str):
                 return False, "PR title is not valid text"
-            if len(pr_title) > MAX_PR_TITLE_LENGTH:
+            if len(pr_title) == MAX_PR_TITLE_LENGTH:
                 return (
                     False,
                     f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
...................................................F
=================================== FAILURES ===================================
__________ test_is_pr_eligible_rejects_overlong_title_before_matching __________

    def test_is_pr_eligible_rejects_overlong_title_before_matching():
        """Test webhook title input is bounded before regex evaluation."""
        config = RepoConfig.from_toml('required_title_patterns = [".*"]')
        config._compiled_title_patterns[0] = MagicMock()
    
        is_eligible, reason = config.is_pr_eligible([], "a" * (MAX_PR_TITLE_LENGTH + 1), "someuser")
    
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:426: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_rejects_overlong_title_before_matching - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 51 passed, 3 deselected, 1 warning in 1.32s
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 0, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -225,7 +225,7 @@
         """
         if not isinstance(patterns, list):
             raise ValueError("required_title_patterns must be a list of strings")
-        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
+        if len(patterns) != MAX_TITLE_PATTERN_COUNT:
             raise ValueError(
                 "required_title_patterns contains too many patterns "
                 f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = []

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) != MAX_TITLE_PATTERN_COUNT:
>           raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
E           ValueError: required_title_patterns contains too many patterns (maximum: 20)

stampbot/config.py:229: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: required_title_patterns contains too many patterns (maximum: 20)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.30s
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 1, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -235,7 +235,7 @@
         for pattern in patterns:
             if not isinstance(pattern, str):
                 raise ValueError("required_title_patterns must contain only strings")
-            if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
+            if len(pattern) != MAX_TITLE_PATTERN_LENGTH:
                 raise ValueError(
                     "required_title_patterns contains a pattern longer than "
                     f"{MAX_TITLE_PATTERN_LENGTH} characters"
....................................F
=================================== FAILURES ===================================
________________ test_repo_config_with_required_title_patterns _________________

    def test_repo_config_with_required_title_patterns():
        """Test parsing repository config with required title patterns."""
        toml_content = """
    required_title_patterns = ["^\\\\[bot\\\\]", "^chore:"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:295: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = ['^\\[bot\\]', '^chore:']

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
            raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
    
        compiled_patterns = []
        for pattern in patterns:
            if not isinstance(pattern, str):
                raise ValueError("required_title_patterns must contain only strings")
            if len(pattern) != MAX_TITLE_PATTERN_LENGTH:
>               raise ValueError(
                    "required_title_patterns contains a pattern longer than "
                    f"{MAX_TITLE_PATTERN_LENGTH} characters"
                )
E               ValueError: required_title_patterns contains a pattern longer than 256 characters

stampbot/config.py:239: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_with_required_title_patterns - ValueError: required_title_patterns contains a pattern longer than 256 characters
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 36 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceComparisonOperator_Gt_NotEq, occurrence: 2, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -309,7 +309,7 @@
         if self._compiled_title_patterns:
             if not isinstance(pr_title, str):
                 return False, "PR title is not valid text"
-            if len(pr_title) > MAX_PR_TITLE_LENGTH:
+            if len(pr_title) != MAX_PR_TITLE_LENGTH:
                 return (
                     False,
                     f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
...............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_matches ___________________

    def test_is_pr_eligible_title_pattern_matches():
        """Test is_pr_eligible returns True when title matches pattern."""
        config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
        is_eligible, reason = config.is_pr_eligible([], "chore: update dependencies", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:386: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_matches - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected, 1 warning in 1.31s
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 0, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -225,7 +225,7 @@
         """
         if not isinstance(patterns, list):
             raise ValueError("required_title_patterns must be a list of strings")
-        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
+        if len(patterns) < MAX_TITLE_PATTERN_COUNT:
             raise ValueError(
                 "required_title_patterns contains too many patterns "
                 f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = []

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) < MAX_TITLE_PATTERN_COUNT:
>           raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
E           ValueError: required_title_patterns contains too many patterns (maximum: 20)

stampbot/config.py:229: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: required_title_patterns contains too many patterns (maximum: 20)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.31s
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 1, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -235,7 +235,7 @@
         for pattern in patterns:
             if not isinstance(pattern, str):
                 raise ValueError("required_title_patterns must contain only strings")
-            if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
+            if len(pattern) < MAX_TITLE_PATTERN_LENGTH:
                 raise ValueError(
                     "required_title_patterns contains a pattern longer than "
                     f"{MAX_TITLE_PATTERN_LENGTH} characters"
....................................F
=================================== FAILURES ===================================
________________ test_repo_config_with_required_title_patterns _________________

    def test_repo_config_with_required_title_patterns():
        """Test parsing repository config with required title patterns."""
        toml_content = """
    required_title_patterns = ["^\\\\[bot\\\\]", "^chore:"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:295: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = ['^\\[bot\\]', '^chore:']

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
            raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
    
        compiled_patterns = []
        for pattern in patterns:
            if not isinstance(pattern, str):
                raise ValueError("required_title_patterns must contain only strings")
            if len(pattern) < MAX_TITLE_PATTERN_LENGTH:
>               raise ValueError(
                    "required_title_patterns contains a pattern longer than "
                    f"{MAX_TITLE_PATTERN_LENGTH} characters"
                )
E               ValueError: required_title_patterns contains a pattern longer than 256 characters

stampbot/config.py:239: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_with_required_title_patterns - ValueError: required_title_patterns contains a pattern longer than 256 characters
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 36 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceComparisonOperator_Gt_Lt, occurrence: 2, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -309,7 +309,7 @@
         if self._compiled_title_patterns:
             if not isinstance(pr_title, str):
                 return False, "PR title is not valid text"
-            if len(pr_title) > MAX_PR_TITLE_LENGTH:
+            if len(pr_title) < MAX_PR_TITLE_LENGTH:
                 return (
                     False,
                     f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
...............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_matches ___________________

    def test_is_pr_eligible_title_pattern_matches():
        """Test is_pr_eligible returns True when title matches pattern."""
        config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
        is_eligible, reason = config.is_pr_eligible([], "chore: update dependencies", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:386: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_matches - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 0, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -225,7 +225,7 @@
         """
         if not isinstance(patterns, list):
             raise ValueError("required_title_patterns must be a list of strings")
-        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
+        if len(patterns) <= MAX_TITLE_PATTERN_COUNT:
             raise ValueError(
                 "required_title_patterns contains too many patterns "
                 f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = []

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) <= MAX_TITLE_PATTERN_COUNT:
>           raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
E           ValueError: required_title_patterns contains too many patterns (maximum: 20)

stampbot/config.py:229: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: required_title_patterns contains too many patterns (maximum: 20)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.32s
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 1, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -235,7 +235,7 @@
         for pattern in patterns:
             if not isinstance(pattern, str):
                 raise ValueError("required_title_patterns must contain only strings")
-            if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
+            if len(pattern) <= MAX_TITLE_PATTERN_LENGTH:
                 raise ValueError(
                     "required_title_patterns contains a pattern longer than "
                     f"{MAX_TITLE_PATTERN_LENGTH} characters"
....................................F
=================================== FAILURES ===================================
________________ test_repo_config_with_required_title_patterns _________________

    def test_repo_config_with_required_title_patterns():
        """Test parsing repository config with required title patterns."""
        toml_content = """
    required_title_patterns = ["^\\\\[bot\\\\]", "^chore:"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:295: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = ['^\\[bot\\]', '^chore:']

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
            raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
    
        compiled_patterns = []
        for pattern in patterns:
            if not isinstance(pattern, str):
                raise ValueError("required_title_patterns must contain only strings")
            if len(pattern) <= MAX_TITLE_PATTERN_LENGTH:
>               raise ValueError(
                    "required_title_patterns contains a pattern longer than "
                    f"{MAX_TITLE_PATTERN_LENGTH} characters"
                )
E               ValueError: required_title_patterns contains a pattern longer than 256 characters

stampbot/config.py:239: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_with_required_title_patterns - ValueError: required_title_patterns contains a pattern longer than 256 characters
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 36 passed, 3 deselected, 1 warning in 1.32s
operator: core/ReplaceComparisonOperator_Gt_LtE, occurrence: 2, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -309,7 +309,7 @@
         if self._compiled_title_patterns:
             if not isinstance(pr_title, str):
                 return False, "PR title is not valid text"
-            if len(pr_title) > MAX_PR_TITLE_LENGTH:
+            if len(pr_title) <= MAX_PR_TITLE_LENGTH:
                 return (
                     False,
                     f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
...............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_matches ___________________

    def test_is_pr_eligible_title_pattern_matches():
        """Test is_pr_eligible returns True when title matches pattern."""
        config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
        is_eligible, reason = config.is_pr_eligible([], "chore: update dependencies", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:386: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_matches - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 0, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -225,7 +225,7 @@
         """
         if not isinstance(patterns, list):
             raise ValueError("required_title_patterns must be a list of strings")
-        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
+        if len(patterns) >= MAX_TITLE_PATTERN_COUNT:
             raise ValueError(
                 "required_title_patterns contains too many patterns "
                 f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
..........................................F
=================================== FAILURES ===================================
______________ test_repo_config_accepts_title_pattern_boundaries _______________

    def test_repo_config_accepts_title_pattern_boundaries():
        """Test pattern count and length limits are inclusive."""
        patterns = ["a" * MAX_TITLE_PATTERN_LENGTH]
        patterns.extend("^fix:" for _ in range(MAX_TITLE_PATTERN_COUNT - 1))
    
>       config = RepoConfig.from_toml(f"required_title_patterns = {patterns!r}")
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:344: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', '^fix:', '^fix:', '^fix:', '^fix:', '^fix:', ...]

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) >= MAX_TITLE_PATTERN_COUNT:
>           raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
E           ValueError: required_title_patterns contains too many patterns (maximum: 20)

stampbot/config.py:229: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_accepts_title_pattern_boundaries - ValueError: required_title_patterns contains too many patterns (maximum: 20)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 42 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 1, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -235,7 +235,7 @@
         for pattern in patterns:
             if not isinstance(pattern, str):
                 raise ValueError("required_title_patterns must contain only strings")
-            if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
+            if len(pattern) >= MAX_TITLE_PATTERN_LENGTH:
                 raise ValueError(
                     "required_title_patterns contains a pattern longer than "
                     f"{MAX_TITLE_PATTERN_LENGTH} characters"
..........................................F
=================================== FAILURES ===================================
______________ test_repo_config_accepts_title_pattern_boundaries _______________

    def test_repo_config_accepts_title_pattern_boundaries():
        """Test pattern count and length limits are inclusive."""
        patterns = ["a" * MAX_TITLE_PATTERN_LENGTH]
        patterns.extend("^fix:" for _ in range(MAX_TITLE_PATTERN_COUNT - 1))
    
>       config = RepoConfig.from_toml(f"required_title_patterns = {patterns!r}")
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:344: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = ['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', '^fix:', '^fix:', '^fix:', '^fix:', '^fix:', ...]

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
            raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
    
        compiled_patterns = []
        for pattern in patterns:
            if not isinstance(pattern, str):
                raise ValueError("required_title_patterns must contain only strings")
            if len(pattern) >= MAX_TITLE_PATTERN_LENGTH:
>               raise ValueError(
                    "required_title_patterns contains a pattern longer than "
                    f"{MAX_TITLE_PATTERN_LENGTH} characters"
                )
E               ValueError: required_title_patterns contains a pattern longer than 256 characters

stampbot/config.py:239: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_accepts_title_pattern_boundaries - ValueError: required_title_patterns contains a pattern longer than 256 characters
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 42 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceComparisonOperator_Gt_GtE, occurrence: 2, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -309,7 +309,7 @@
         if self._compiled_title_patterns:
             if not isinstance(pr_title, str):
                 return False, "PR title is not valid text"
-            if len(pr_title) > MAX_PR_TITLE_LENGTH:
+            if len(pr_title) >= MAX_PR_TITLE_LENGTH:
                 return (
                     False,
                     f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
        is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )
    
        assert is_eligible is False
>       assert f"{int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)} ms safety limit" in reason
E       AssertionError: assert '10 ms safety limit' in 'PR title exceeds the 256-character safety limit'

tests/test_config.py:450: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - AssertionError: assert '10 ms safety limit' in 'PR title exceeds the 256-character safety limit'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 0, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -225,7 +225,7 @@
         """
         if not isinstance(patterns, list):
             raise ValueError("required_title_patterns must be a list of strings")
-        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
+        if len(patterns) is MAX_TITLE_PATTERN_COUNT:
             raise ValueError(
                 "required_title_patterns contains too many patterns "
                 f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
........................................F
=================================== FAILURES ===================================
_______________ test_repo_config_rejects_too_many_title_patterns _______________

    def test_repo_config_rejects_too_many_title_patterns():
        """Test title pattern count is bounded."""
        patterns = ", ".join('"^fix:"' for _ in range(MAX_TITLE_PATTERN_COUNT + 1))
    
>       with pytest.raises(ValueError, match="too many patterns"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_config.py:327: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_rejects_too_many_title_patterns - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 40 passed, 3 deselected, 1 warning in 1.32s
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 1, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -235,7 +235,7 @@
         for pattern in patterns:
             if not isinstance(pattern, str):
                 raise ValueError("required_title_patterns must contain only strings")
-            if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
+            if len(pattern) is MAX_TITLE_PATTERN_LENGTH:
                 raise ValueError(
                     "required_title_patterns contains a pattern longer than "
                     f"{MAX_TITLE_PATTERN_LENGTH} characters"
.........................................F
=================================== FAILURES ===================================
_________________ test_repo_config_rejects_long_title_pattern __________________

    def test_repo_config_rejects_long_title_pattern():
        """Test individual title pattern length is bounded."""
        pattern = "a" * (MAX_TITLE_PATTERN_LENGTH + 1)
    
>       with pytest.raises(ValueError, match="pattern longer than"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_config.py:335: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_rejects_long_title_pattern - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 41 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceComparisonOperator_Gt_Is, occurrence: 2, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -309,7 +309,7 @@
         if self._compiled_title_patterns:
             if not isinstance(pr_title, str):
                 return False, "PR title is not valid text"
-            if len(pr_title) > MAX_PR_TITLE_LENGTH:
+            if len(pr_title) is MAX_PR_TITLE_LENGTH:
                 return (
                     False,
                     f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
...................................................F
=================================== FAILURES ===================================
__________ test_is_pr_eligible_rejects_overlong_title_before_matching __________

    def test_is_pr_eligible_rejects_overlong_title_before_matching():
        """Test webhook title input is bounded before regex evaluation."""
        config = RepoConfig.from_toml('required_title_patterns = [".*"]')
        config._compiled_title_patterns[0] = MagicMock()
    
        is_eligible, reason = config.is_pr_eligible([], "a" * (MAX_PR_TITLE_LENGTH + 1), "someuser")
    
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:426: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_rejects_overlong_title_before_matching - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 51 passed, 3 deselected, 1 warning in 1.35s
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 0, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -225,7 +225,7 @@
         """
         if not isinstance(patterns, list):
             raise ValueError("required_title_patterns must be a list of strings")
-        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
+        if len(patterns) is not MAX_TITLE_PATTERN_COUNT:
             raise ValueError(
                 "required_title_patterns contains too many patterns "
                 f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = []

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) is not MAX_TITLE_PATTERN_COUNT:
>           raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
E           ValueError: required_title_patterns contains too many patterns (maximum: 20)

stampbot/config.py:229: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: required_title_patterns contains too many patterns (maximum: 20)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.29s
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 1, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -235,7 +235,7 @@
         for pattern in patterns:
             if not isinstance(pattern, str):
                 raise ValueError("required_title_patterns must contain only strings")
-            if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
+            if len(pattern) is not MAX_TITLE_PATTERN_LENGTH:
                 raise ValueError(
                     "required_title_patterns contains a pattern longer than "
                     f"{MAX_TITLE_PATTERN_LENGTH} characters"
....................................F
=================================== FAILURES ===================================
________________ test_repo_config_with_required_title_patterns _________________

    def test_repo_config_with_required_title_patterns():
        """Test parsing repository config with required title patterns."""
        toml_content = """
    required_title_patterns = ["^\\\\[bot\\\\]", "^chore:"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:295: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = ['^\\[bot\\]', '^chore:']

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
            raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
    
        compiled_patterns = []
        for pattern in patterns:
            if not isinstance(pattern, str):
                raise ValueError("required_title_patterns must contain only strings")
            if len(pattern) is not MAX_TITLE_PATTERN_LENGTH:
>               raise ValueError(
                    "required_title_patterns contains a pattern longer than "
                    f"{MAX_TITLE_PATTERN_LENGTH} characters"
                )
E               ValueError: required_title_patterns contains a pattern longer than 256 characters

stampbot/config.py:239: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_with_required_title_patterns - ValueError: required_title_patterns contains a pattern longer than 256 characters
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 36 passed, 3 deselected, 1 warning in 1.31s
operator: core/ReplaceComparisonOperator_Gt_IsNot, occurrence: 2, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -309,7 +309,7 @@
         if self._compiled_title_patterns:
             if not isinstance(pr_title, str):
                 return False, "PR title is not valid text"
-            if len(pr_title) > MAX_PR_TITLE_LENGTH:
+            if len(pr_title) is not MAX_PR_TITLE_LENGTH:
                 return (
                     False,
                     f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
...............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_matches ___________________

    def test_is_pr_eligible_title_pattern_matches():
        """Test is_pr_eligible returns True when title matches pattern."""
        config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
        is_eligible, reason = config.is_pr_eligible([], "chore: update dependencies", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:386: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_matches - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected, 1 warning in 1.31s
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 0, definition_name: __init__
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -162,7 +162,7 @@
             "unapprove_commands",
         )
         self.required_labels = required_labels or []
-        title_patterns = required_title_patterns if required_title_patterns is not None else []
+        title_patterns = required_title_patterns if required_title_patterns is None else []
         self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
         self.required_title_patterns = list(title_patterns)
         self.allowed_users = allowed_users or []
...............................F
=================================== FAILURES ===================================
________________ test_repo_config_detaches_valid_command_lists _________________

    def test_repo_config_detaches_valid_command_lists():
        """Test a caller cannot mutate command settings through its input lists."""
        approve_commands = ["approve"]
        unapprove_commands = ["unapprove"]
    
>       config = RepoConfig(
            approval_labels=[],
            auto_approve_on_label=True,
            reapprove=False,
            chatops_enabled=True,
            chatops_required_permission="maintain",
            approve_commands=approve_commands,
            unapprove_commands=unapprove_commands,
        )

tests/test_config.py:205: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = None

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
>           raise ValueError("required_title_patterns must be a list of strings")
E           ValueError: required_title_patterns must be a list of strings

stampbot/config.py:227: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_detaches_valid_command_lists - ValueError: required_title_patterns must be a list of strings
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 31 passed, 3 deselected, 1 warning in 1.35s
operator: core/ReplaceUnaryOperator_USub_UAdd, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -345,7 +345,7 @@
             if self.allowed_teams and author_team_slugs:
                 for team in self.allowed_teams:
                     # Support both "org/team" and "team" formats
-                    team_slug = team.split("/")[-1] if "/" in team else team
+                    team_slug = team.split("/")[+1] if "/" in team else team
                     if team_slug in author_team_slugs:
                         return True, None
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceUnaryOperator_USub_Invert, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -345,7 +345,7 @@
             if self.allowed_teams and author_team_slugs:
                 for team in self.allowed_teams:
                     # Support both "org/team" and "team" formats
-                    team_slug = team.split("/")[-1] if "/" in team else team
+                    team_slug = team.split("/")[~1] if "/" in team else team
                     if team_slug in author_team_slugs:
                         return True, None
 
............................................................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________

    def test_is_pr_eligible_allowed_team():
        """Test is_pr_eligible returns True when author is in an allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "team-member", author_team_slugs=["release-team"]
        )
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:523: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected, 1 warning in 1.35s
operator: core/ReplaceUnaryOperator_USub_Not, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -345,7 +345,7 @@
             if self.allowed_teams and author_team_slugs:
                 for team in self.allowed_teams:
                     # Support both "org/team" and "team" formats
-                    team_slug = team.split("/")[-1] if "/" in team else team
+                    team_slug = team.split("/")[not 1] if "/" in team else team
                     if team_slug in author_team_slugs:
                         return True, None
 
............................................................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________

    def test_is_pr_eligible_allowed_team():
        """Test is_pr_eligible returns True when author is in an allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "team-member", author_team_slugs=["release-team"]
        )
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:523: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceUnaryOperator_Delete_USub, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -345,7 +345,7 @@
             if self.allowed_teams and author_team_slugs:
                 for team in self.allowed_teams:
                     # Support both "org/team" and "team" formats
-                    team_slug = team.split("/")[-1] if "/" in team else team
+                    team_slug = team.split("/")[1] if "/" in team else team
                     if team_slug in author_team_slugs:
                         return True, None
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -84,7 +84,7 @@
     # Dynaconf parses environment values, so ``false`` arrives as a bool and
     # ``1.9`` as a float. Neither is an integer setting, and int() would accept
     # both silently.
-    if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
+    if isinstance(raw, bool) or (isinstance(raw, float) and  raw.is_integer()):
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
     try:
         seconds = int(raw)
........................................................................ [ 15%]
F
=================================== FAILURES ===================================
____________ test_repo_config_cache_seconds_accepts_integers[5.0-5] ____________

raw = 5.0, expected = 5

    @pytest.mark.parametrize(
        ("raw", "expected"),
        [(300, 300), ("120", 120), (0, 0), (5.0, 5)],
    )
    def test_repo_config_cache_seconds_accepts_integers(raw, expected):
        """Test the cache lifetime accepts integer-valued settings."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = raw
    
>           assert repo_config_cache_seconds() == expected
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:627: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def repo_config_cache_seconds() -> int:
        """Return how long a repository's parsed policy stays cached, in seconds.
    
        Returns:
            A non-negative integer. Zero disables the cache.
    
        Raises:
            ValueError: If ``repo_config_cache_seconds`` is not a non-negative integer.
        """
        raw = settings.get("repo_config_cache_seconds", DEFAULT_REPO_CONFIG_CACHE_SECONDS)
        # Dynaconf parses environment values, so ``false`` arrives as a bool and
        # ``1.9`` as a float. Neither is an integer setting, and int() would accept
        # both silently.
        if isinstance(raw, bool) or (isinstance(raw, float) and  raw.is_integer()):
>           raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
E           ValueError: repo_config_cache_seconds must be an integer, got 5.0

stampbot/config.py:88: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_cache_seconds_accepts_integers[5.0-5] - ValueError: repo_config_cache_seconds must be an integer, got 5.0
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 72 passed, 3 deselected, 1 warning in 1.35s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1, definition_name: _validate_command_list
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -183,7 +183,7 @@
         Raises:
             ValueError: If the setting is not a list of strings.
         """
-        if not isinstance(commands, list):
+        if  isinstance(commands, list):
             raise ValueError(f"{setting} must be a list of strings")
         if any(not isinstance(command, str) for command in commands):
             raise ValueError(f"{setting} must contain only strings")
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:156: in __init__
    self.approve_commands = self._validate_command_list(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

commands = ['approve', 'stamp'], setting = 'approve_commands'

    @staticmethod
    def _validate_command_list(commands: object, setting: str) -> list[str]:
        """Require one command setting to be a list containing only strings.
    
        Args:
            commands: Parsed application or repository setting.
            setting: Setting name used in the validation error.
    
        Returns:
            A detached copy of the validated command list.
    
        Raises:
            ValueError: If the setting is not a list of strings.
        """
        if  isinstance(commands, list):
>           raise ValueError(f"{setting} must be a list of strings")
E           ValueError: approve_commands must be a list of strings

stampbot/config.py:187: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: approve_commands must be a list of strings
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.32s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2, definition_name: _validate_command_list
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -185,7 +185,7 @@
         """
         if not isinstance(commands, list):
             raise ValueError(f"{setting} must be a list of strings")
-        if any(not isinstance(command, str) for command in commands):
+        if any( isinstance(command, str) for command in commands):
             raise ValueError(f"{setting} must contain only strings")
         return list(commands)
 
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:156: in __init__
    self.approve_commands = self._validate_command_list(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

commands = ['approve', 'stamp'], setting = 'approve_commands'

    @staticmethod
    def _validate_command_list(commands: object, setting: str) -> list[str]:
        """Require one command setting to be a list containing only strings.
    
        Args:
            commands: Parsed application or repository setting.
            setting: Setting name used in the validation error.
    
        Returns:
            A detached copy of the validated command list.
    
        Raises:
            ValueError: If the setting is not a list of strings.
        """
        if not isinstance(commands, list):
            raise ValueError(f"{setting} must be a list of strings")
        if any( isinstance(command, str) for command in commands):
>           raise ValueError(f"{setting} must contain only strings")
E           ValueError: approve_commands must contain only strings

stampbot/config.py:189: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: approve_commands must contain only strings
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.31s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 3, definition_name: _validate_required_permission
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -202,7 +202,7 @@
         Raises:
             ValueError: If the permission is not one of GitHub's repository levels.
         """
-        if not isinstance(permission, str) or permission not in REPO_PERMISSION_LEVELS:
+        if  isinstance(permission, str) or permission not in REPO_PERMISSION_LEVELS:
             raise ValueError(
                 "Invalid chatops_required_permission: "
                 f"{permission}. "
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:153: in __init__
    self.chatops_required_permission = self._validate_required_permission(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

permission = 'write'

    @staticmethod
    def _validate_required_permission(permission: object) -> str:
        """Require the ChatOps permission threshold to be a supported value.
    
        Args:
            permission: Parsed application or repository setting.
    
        Returns:
            The validated permission name.
    
        Raises:
            ValueError: If the permission is not one of GitHub's repository levels.
        """
        if  isinstance(permission, str) or permission not in REPO_PERMISSION_LEVELS:
>           raise ValueError(
                "Invalid chatops_required_permission: "
                f"{permission}. "
                f"Valid values: {', '.join(REPO_PERMISSION_LEVELS)}"
            )
E           ValueError: Invalid chatops_required_permission: write. Valid values: none, read, triage, write, maintain, admin

stampbot/config.py:206: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: Invalid chatops_required_permission: write. Valid values: none, read, triage, write, maintain, admin
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 4, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -223,7 +223,7 @@
         Raises:
             ValueError: If the pattern list, an item, or a pattern is invalid.
         """
-        if not isinstance(patterns, list):
+        if  isinstance(patterns, list):
             raise ValueError("required_title_patterns must be a list of strings")
         if len(patterns) > MAX_TITLE_PATTERN_COUNT:
             raise ValueError(
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = []

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if  isinstance(patterns, list):
>           raise ValueError("required_title_patterns must be a list of strings")
E           ValueError: required_title_patterns must be a list of strings

stampbot/config.py:227: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: required_title_patterns must be a list of strings
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.30s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 5, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -233,7 +233,7 @@
 
         compiled_patterns = []
         for pattern in patterns:
-            if not isinstance(pattern, str):
+            if  isinstance(pattern, str):
                 raise ValueError("required_title_patterns must contain only strings")
             if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
                 raise ValueError(
....................................F
=================================== FAILURES ===================================
________________ test_repo_config_with_required_title_patterns _________________

    def test_repo_config_with_required_title_patterns():
        """Test parsing repository config with required title patterns."""
        toml_content = """
    required_title_patterns = ["^\\\\[bot\\\\]", "^chore:"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:295: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = ['^\\[bot\\]', '^chore:']

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
            raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
    
        compiled_patterns = []
        for pattern in patterns:
            if  isinstance(pattern, str):
>               raise ValueError("required_title_patterns must contain only strings")
E               ValueError: required_title_patterns must contain only strings

stampbot/config.py:237: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_with_required_title_patterns - ValueError: required_title_patterns must contain only strings
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 36 passed, 3 deselected, 1 warning in 1.32s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 6, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -299,7 +299,7 @@
         """
         # Check required labels filter
         if self.required_labels:
-            if not any(label in self.required_labels for label in pr_labels):
+            if  any(label in self.required_labels for label in pr_labels):
                 return (
                     False,
                     f"PR missing required label (one of: {', '.join(self.required_labels)})",
............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_required_label_present __________________

    def test_is_pr_eligible_required_label_present():
        """Test is_pr_eligible returns True when required label is present."""
        config = RepoConfig.from_toml('required_labels = ["dependencies", "automated"]')
        is_eligible, reason = config.is_pr_eligible(["dependencies"], "PR title", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:361: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_required_label_present - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 44 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 7, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -307,7 +307,7 @@
 
         # Check required title patterns filter
         if self._compiled_title_patterns:
-            if not isinstance(pr_title, str):
+            if  isinstance(pr_title, str):
                 return False, "PR title is not valid text"
             if len(pr_title) > MAX_PR_TITLE_LENGTH:
                 return (
...............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_matches ___________________

    def test_is_pr_eligible_title_pattern_matches():
        """Test is_pr_eligible returns True when title matches pattern."""
        config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
        is_eligible, reason = config.is_pr_eligible([], "chore: update dependencies", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:386: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_matches - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 8, definition_name: needs_team_check
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -368,7 +368,7 @@
         Returns:
             True if team check is needed (author not in allowed_users but teams configured)
         """
-        if not self.allowed_teams:
+        if  self.allowed_teams:
             return False
         if self.allowed_users and pr_author in self.allowed_users:
             return False
..................................................................F
=================================== FAILURES ===================================
__________________ test_needs_team_check_no_teams_configured ___________________

    def test_needs_team_check_no_teams_configured():
        """Test needs_team_check returns False when no teams configured."""
        config = RepoConfig.from_toml('allowed_users = ["some-user"]')
>       assert config.needs_team_check("any-user") is False
E       AssertionError: assert True is False
E        +  where True = needs_team_check('any-user')
E        +    where needs_team_check = <stampbot.config.RepoConfig object at 0x771672620a10>.needs_team_check

tests/test_config.py:595: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_no_teams_configured - AssertionError: assert True is False
 +  where True = needs_team_check('any-user')
 +    where needs_team_check = <stampbot.config.RepoConfig object at 0x771672620a10>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 66 passed, 3 deselected, 1 warning in 1.35s
operator: core/AddNot, occurrence: 0, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -84,7 +84,7 @@
     # Dynaconf parses environment values, so ``false`` arrives as a bool and
     # ``1.9`` as a float. Neither is an integer setting, and int() would accept
     # both silently.
-    if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
+    if not isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
     try:
         seconds = int(raw)
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
stampbot/webhook_handler.py:1149: in <module>
    webhook_handler = WebhookHandler()
                      ^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:84: in __init__
    self._policy = RepoPolicyResolver(repo_config_cache_seconds())
                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/config.py:88: in repo_config_cache_seconds
    raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
E   ValueError: repo_config_cache_seconds must be an integer, got 300
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: repo_config_cache_seconds must be an integer, got 300
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.03s
operator: core/AddNot, occurrence: 1, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -90,7 +90,7 @@
         seconds = int(raw)
     except (TypeError, ValueError) as error:
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
-    if seconds < 0:
+    if not seconds < 0:
         raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
     return seconds
 
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:48: in <module>
    from stampbot.webhook_handler import webhook_handler
stampbot/webhook_handler.py:1149: in <module>
    webhook_handler = WebhookHandler()
                      ^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:84: in __init__
    self._policy = RepoPolicyResolver(repo_config_cache_seconds())
                                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/config.py:94: in repo_config_cache_seconds
    raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
E   ValueError: repo_config_cache_seconds must be 0 or greater, got 300
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - ValueError: repo_config_cache_seconds must be 0 or greater, got 300
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.03s
operator: core/AddNot, occurrence: 2, definition_name: __init__
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -162,7 +162,7 @@
             "unapprove_commands",
         )
         self.required_labels = required_labels or []
-        title_patterns = required_title_patterns if required_title_patterns is not None else []
+        title_patterns = required_title_patterns if not required_title_patterns is not None else []
         self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
         self.required_title_patterns = list(title_patterns)
         self.allowed_users = allowed_users or []
...............................F
=================================== FAILURES ===================================
________________ test_repo_config_detaches_valid_command_lists _________________

    def test_repo_config_detaches_valid_command_lists():
        """Test a caller cannot mutate command settings through its input lists."""
        approve_commands = ["approve"]
        unapprove_commands = ["unapprove"]
    
>       config = RepoConfig(
            approval_labels=[],
            auto_approve_on_label=True,
            reapprove=False,
            chatops_enabled=True,
            chatops_required_permission="maintain",
            approve_commands=approve_commands,
            unapprove_commands=unapprove_commands,
        )

tests/test_config.py:205: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = None

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
>           raise ValueError("required_title_patterns must be a list of strings")
E           ValueError: required_title_patterns must be a list of strings

stampbot/config.py:227: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_detaches_valid_command_lists - ValueError: required_title_patterns must be a list of strings
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 31 passed, 3 deselected, 1 warning in 1.33s
operator: core/AddNot, occurrence: 3, definition_name: _validate_command_list
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -183,7 +183,7 @@
         Raises:
             ValueError: If the setting is not a list of strings.
         """
-        if not isinstance(commands, list):
+        if not not isinstance(commands, list):
             raise ValueError(f"{setting} must be a list of strings")
         if any(not isinstance(command, str) for command in commands):
             raise ValueError(f"{setting} must contain only strings")
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:156: in __init__
    self.approve_commands = self._validate_command_list(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

commands = ['approve', 'stamp'], setting = 'approve_commands'

    @staticmethod
    def _validate_command_list(commands: object, setting: str) -> list[str]:
        """Require one command setting to be a list containing only strings.
    
        Args:
            commands: Parsed application or repository setting.
            setting: Setting name used in the validation error.
    
        Returns:
            A detached copy of the validated command list.
    
        Raises:
            ValueError: If the setting is not a list of strings.
        """
        if not not isinstance(commands, list):
>           raise ValueError(f"{setting} must be a list of strings")
E           ValueError: approve_commands must be a list of strings

stampbot/config.py:187: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: approve_commands must be a list of strings
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.32s
operator: core/AddNot, occurrence: 4, definition_name: _validate_command_list
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -185,7 +185,7 @@
         """
         if not isinstance(commands, list):
             raise ValueError(f"{setting} must be a list of strings")
-        if any(not isinstance(command, str) for command in commands):
+        if not any(not isinstance(command, str) for command in commands):
             raise ValueError(f"{setting} must contain only strings")
         return list(commands)
 
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:156: in __init__
    self.approve_commands = self._validate_command_list(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

commands = ['approve', 'stamp'], setting = 'approve_commands'

    @staticmethod
    def _validate_command_list(commands: object, setting: str) -> list[str]:
        """Require one command setting to be a list containing only strings.
    
        Args:
            commands: Parsed application or repository setting.
            setting: Setting name used in the validation error.
    
        Returns:
            A detached copy of the validated command list.
    
        Raises:
            ValueError: If the setting is not a list of strings.
        """
        if not isinstance(commands, list):
            raise ValueError(f"{setting} must be a list of strings")
        if not any(not isinstance(command, str) for command in commands):
>           raise ValueError(f"{setting} must contain only strings")
E           ValueError: approve_commands must contain only strings

stampbot/config.py:189: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: approve_commands must contain only strings
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.35s
operator: core/AddNot, occurrence: 5, definition_name: _validate_required_permission
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -202,7 +202,7 @@
         Raises:
             ValueError: If the permission is not one of GitHub's repository levels.
         """
-        if not isinstance(permission, str) or permission not in REPO_PERMISSION_LEVELS:
+        if not not isinstance(permission, str) or permission not in REPO_PERMISSION_LEVELS:
             raise ValueError(
                 "Invalid chatops_required_permission: "
                 f"{permission}. "
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:153: in __init__
    self.chatops_required_permission = self._validate_required_permission(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

permission = 'write'

    @staticmethod
    def _validate_required_permission(permission: object) -> str:
        """Require the ChatOps permission threshold to be a supported value.
    
        Args:
            permission: Parsed application or repository setting.
    
        Returns:
            The validated permission name.
    
        Raises:
            ValueError: If the permission is not one of GitHub's repository levels.
        """
        if not not isinstance(permission, str) or permission not in REPO_PERMISSION_LEVELS:
>           raise ValueError(
                "Invalid chatops_required_permission: "
                f"{permission}. "
                f"Valid values: {', '.join(REPO_PERMISSION_LEVELS)}"
            )
E           ValueError: Invalid chatops_required_permission: write. Valid values: none, read, triage, write, maintain, admin

stampbot/config.py:206: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: Invalid chatops_required_permission: write. Valid values: none, read, triage, write, maintain, admin
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.30s
operator: core/AddNot, occurrence: 6, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -223,7 +223,7 @@
         Raises:
             ValueError: If the pattern list, an item, or a pattern is invalid.
         """
-        if not isinstance(patterns, list):
+        if not not isinstance(patterns, list):
             raise ValueError("required_title_patterns must be a list of strings")
         if len(patterns) > MAX_TITLE_PATTERN_COUNT:
             raise ValueError(
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = []

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not not isinstance(patterns, list):
>           raise ValueError("required_title_patterns must be a list of strings")
E           ValueError: required_title_patterns must be a list of strings

stampbot/config.py:227: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: required_title_patterns must be a list of strings
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.33s
operator: core/AddNot, occurrence: 7, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -225,7 +225,7 @@
         """
         if not isinstance(patterns, list):
             raise ValueError("required_title_patterns must be a list of strings")
-        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
+        if not len(patterns) > MAX_TITLE_PATTERN_COUNT:
             raise ValueError(
                 "required_title_patterns contains too many patterns "
                 f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = []

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if not len(patterns) > MAX_TITLE_PATTERN_COUNT:
>           raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
E           ValueError: required_title_patterns contains too many patterns (maximum: 20)

stampbot/config.py:229: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - ValueError: required_title_patterns contains too many patterns (maximum: 20)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.30s
operator: core/AddNot, occurrence: 8, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -233,7 +233,7 @@
 
         compiled_patterns = []
         for pattern in patterns:
-            if not isinstance(pattern, str):
+            if not not isinstance(pattern, str):
                 raise ValueError("required_title_patterns must contain only strings")
             if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
                 raise ValueError(
....................................F
=================================== FAILURES ===================================
________________ test_repo_config_with_required_title_patterns _________________

    def test_repo_config_with_required_title_patterns():
        """Test parsing repository config with required title patterns."""
        toml_content = """
    required_title_patterns = ["^\\\\[bot\\\\]", "^chore:"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:295: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = ['^\\[bot\\]', '^chore:']

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
            raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
    
        compiled_patterns = []
        for pattern in patterns:
            if not not isinstance(pattern, str):
>               raise ValueError("required_title_patterns must contain only strings")
E               ValueError: required_title_patterns must contain only strings

stampbot/config.py:237: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_with_required_title_patterns - ValueError: required_title_patterns must contain only strings
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 36 passed, 3 deselected, 1 warning in 1.35s
operator: core/AddNot, occurrence: 9, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -235,7 +235,7 @@
         for pattern in patterns:
             if not isinstance(pattern, str):
                 raise ValueError("required_title_patterns must contain only strings")
-            if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
+            if not len(pattern) > MAX_TITLE_PATTERN_LENGTH:
                 raise ValueError(
                     "required_title_patterns contains a pattern longer than "
                     f"{MAX_TITLE_PATTERN_LENGTH} characters"
....................................F
=================================== FAILURES ===================================
________________ test_repo_config_with_required_title_patterns _________________

    def test_repo_config_with_required_title_patterns():
        """Test parsing repository config with required title patterns."""
        toml_content = """
    required_title_patterns = ["^\\\\[bot\\\\]", "^chore:"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:295: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = ['^\\[bot\\]', '^chore:']

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
            raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
    
        compiled_patterns = []
        for pattern in patterns:
            if not isinstance(pattern, str):
                raise ValueError("required_title_patterns must contain only strings")
            if not len(pattern) > MAX_TITLE_PATTERN_LENGTH:
>               raise ValueError(
                    "required_title_patterns contains a pattern longer than "
                    f"{MAX_TITLE_PATTERN_LENGTH} characters"
                )
E               ValueError: required_title_patterns contains a pattern longer than 256 characters

stampbot/config.py:239: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_with_required_title_patterns - ValueError: required_title_patterns contains a pattern longer than 256 characters
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 36 passed, 3 deselected, 1 warning in 1.35s
operator: core/AddNot, occurrence: 10, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -298,7 +298,7 @@
             Tuple of (is_eligible, reason_if_not_eligible)
         """
         # Check required labels filter
-        if self.required_labels:
+        if not self.required_labels:
             if not any(label in self.required_labels for label in pr_labels):
                 return (
                     False,
...........................................F
=================================== FAILURES ===================================
________________________ test_is_pr_eligible_no_filters ________________________

    def test_is_pr_eligible_no_filters():
        """Test is_pr_eligible returns True when no filters configured."""
        config = RepoConfig.default()
        is_eligible, reason = config.is_pr_eligible(["some-label"], "Some PR title", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:353: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_no_filters - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 43 passed, 3 deselected, 1 warning in 1.35s
operator: core/AddNot, occurrence: 11, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -299,7 +299,7 @@
         """
         # Check required labels filter
         if self.required_labels:
-            if not any(label in self.required_labels for label in pr_labels):
+            if not not any(label in self.required_labels for label in pr_labels):
                 return (
                     False,
                     f"PR missing required label (one of: {', '.join(self.required_labels)})",
............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_required_label_present __________________

    def test_is_pr_eligible_required_label_present():
        """Test is_pr_eligible returns True when required label is present."""
        config = RepoConfig.from_toml('required_labels = ["dependencies", "automated"]')
        is_eligible, reason = config.is_pr_eligible(["dependencies"], "PR title", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:361: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_required_label_present - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 44 passed, 3 deselected, 1 warning in 1.34s
operator: core/AddNot, occurrence: 12, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -306,7 +306,7 @@
                 )
 
         # Check required title patterns filter
-        if self._compiled_title_patterns:
+        if not self._compiled_title_patterns:
             if not isinstance(pr_title, str):
                 return False, "PR title is not valid text"
             if len(pr_title) > MAX_PR_TITLE_LENGTH:
...........................................F
=================================== FAILURES ===================================
________________________ test_is_pr_eligible_no_filters ________________________

    def test_is_pr_eligible_no_filters():
        """Test is_pr_eligible returns True when no filters configured."""
        config = RepoConfig.default()
        is_eligible, reason = config.is_pr_eligible(["some-label"], "Some PR title", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:353: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_no_filters - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 43 passed, 3 deselected, 1 warning in 1.30s
operator: core/AddNot, occurrence: 13, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -307,7 +307,7 @@
 
         # Check required title patterns filter
         if self._compiled_title_patterns:
-            if not isinstance(pr_title, str):
+            if not not isinstance(pr_title, str):
                 return False, "PR title is not valid text"
             if len(pr_title) > MAX_PR_TITLE_LENGTH:
                 return (
...............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_matches ___________________

    def test_is_pr_eligible_title_pattern_matches():
        """Test is_pr_eligible returns True when title matches pattern."""
        config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
        is_eligible, reason = config.is_pr_eligible([], "chore: update dependencies", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:386: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_matches - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected, 1 warning in 1.35s
operator: core/AddNot, occurrence: 14, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -309,7 +309,7 @@
         if self._compiled_title_patterns:
             if not isinstance(pr_title, str):
                 return False, "PR title is not valid text"
-            if len(pr_title) > MAX_PR_TITLE_LENGTH:
+            if not len(pr_title) > MAX_PR_TITLE_LENGTH:
                 return (
                     False,
                     f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
...............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_matches ___________________

    def test_is_pr_eligible_title_pattern_matches():
        """Test is_pr_eligible returns True when title matches pattern."""
        config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
        is_eligible, reason = config.is_pr_eligible([], "chore: update dependencies", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:386: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_matches - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected, 1 warning in 1.32s
operator: core/AddNot, occurrence: 15, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -317,7 +317,7 @@
 
             for pattern in self._compiled_title_patterns:
                 try:
-                    if pattern.search(
+                    if not pattern.search(
                         pr_title,
                         timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                         concurrent=True,
................................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_no_match __________________

    def test_is_pr_eligible_title_pattern_no_match():
        """Test is_pr_eligible returns False when title doesn't match any pattern."""
        config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
        is_eligible, reason = config.is_pr_eligible([], "feat: add new feature", "someuser")
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:394: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_no_match - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 48 passed, 3 deselected, 1 warning in 1.31s
operator: core/AddNot, occurrence: 16, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -336,7 +336,7 @@
                 return False, "PR title does not match any required pattern"
 
         # Check allowed users/teams filter (if either is configured)
-        if self.allowed_users or self.allowed_teams:
+        if not self.allowed_users or self.allowed_teams:
             # User is allowed if they're in allowed_users
             if pr_author in self.allowed_users:
                 return True, None
...........................................F
=================================== FAILURES ===================================
________________________ test_is_pr_eligible_no_filters ________________________

    def test_is_pr_eligible_no_filters():
        """Test is_pr_eligible returns True when no filters configured."""
        config = RepoConfig.default()
        is_eligible, reason = config.is_pr_eligible(["some-label"], "Some PR title", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:353: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_no_filters - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 43 passed, 3 deselected, 1 warning in 1.31s
operator: core/AddNot, occurrence: 17, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -338,7 +338,7 @@
         # Check allowed users/teams filter (if either is configured)
         if self.allowed_users or self.allowed_teams:
             # User is allowed if they're in allowed_users
-            if pr_author in self.allowed_users:
+            if not pr_author in self.allowed_users:
                 return True, None
 
             # Or if they're a member of an allowed team
..........................................................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_user _______________________

    def test_is_pr_eligible_allowed_user():
        """Test is_pr_eligible returns True when author is in allowed_users."""
        config = RepoConfig.from_toml('allowed_users = ["dependabot[bot]", "renovate[bot]"]')
        is_eligible, reason = config.is_pr_eligible([], "Update deps", "dependabot[bot]")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:505: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_user - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected, 1 warning in 1.35s
operator: core/AddNot, occurrence: 18, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -342,7 +342,7 @@
                 return True, None
 
             # Or if they're a member of an allowed team
-            if self.allowed_teams and author_team_slugs:
+            if not self.allowed_teams and author_team_slugs:
                 for team in self.allowed_teams:
                     # Support both "org/team" and "team" formats
                     team_slug = team.split("/")[-1] if "/" in team else team
............................................................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________

    def test_is_pr_eligible_allowed_team():
        """Test is_pr_eligible returns True when author is in an allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "team-member", author_team_slugs=["release-team"]
        )
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:523: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected, 1 warning in 1.36s
operator: core/AddNot, occurrence: 19, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -345,7 +345,7 @@
             if self.allowed_teams and author_team_slugs:
                 for team in self.allowed_teams:
                     # Support both "org/team" and "team" formats
-                    team_slug = team.split("/")[-1] if "/" in team else team
+                    team_slug = team.split("/")[-1] if not "/" in team else team
                     if team_slug in author_team_slugs:
                         return True, None
 
............................................................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________

    def test_is_pr_eligible_allowed_team():
        """Test is_pr_eligible returns True when author is in an allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "team-member", author_team_slugs=["release-team"]
        )
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:523: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected, 1 warning in 1.36s
operator: core/AddNot, occurrence: 20, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -346,7 +346,7 @@
                 for team in self.allowed_teams:
                     # Support both "org/team" and "team" formats
                     team_slug = team.split("/")[-1] if "/" in team else team
-                    if team_slug in author_team_slugs:
+                    if not team_slug in author_team_slugs:
                         return True, None
 
             # Neither user nor team matched
............................................................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________

    def test_is_pr_eligible_allowed_team():
        """Test is_pr_eligible returns True when author is in an allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "team-member", author_team_slugs=["release-team"]
        )
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:523: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected, 1 warning in 1.34s
operator: core/AddNot, occurrence: 21, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -350,7 +350,7 @@
                         return True, None
 
             # Neither user nor team matched
-            if self.allowed_users and self.allowed_teams:
+            if not self.allowed_users and self.allowed_teams:
                 return False, "PR author not in allowed users or teams"
             elif self.allowed_users:
                 return False, f"PR author not in allowed users: {', '.join(self.allowed_users)}"
.............................................................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_team_not_matched _____________________

    def test_is_pr_eligible_team_not_matched():
        """Test is_pr_eligible returns False when author is not in any allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "non-member", author_team_slugs=["other-team"]
        )
        assert is_eligible is False
>       assert "not a member of any allowed team" in reason
E       AssertionError: assert 'not a member of any allowed team' in 'PR author not in allowed users or teams'

tests/test_config.py:534: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_team_not_matched - AssertionError: assert 'not a member of any allowed team' in 'PR author not in allowed users or teams'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 61 passed, 3 deselected, 1 warning in 1.33s
operator: core/AddNot, occurrence: 22, definition_name: needs_team_check
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -368,7 +368,7 @@
         Returns:
             True if team check is needed (author not in allowed_users but teams configured)
         """
-        if not self.allowed_teams:
+        if not not self.allowed_teams:
             return False
         if self.allowed_users and pr_author in self.allowed_users:
             return False
..................................................................F
=================================== FAILURES ===================================
__________________ test_needs_team_check_no_teams_configured ___________________

    def test_needs_team_check_no_teams_configured():
        """Test needs_team_check returns False when no teams configured."""
        config = RepoConfig.from_toml('allowed_users = ["some-user"]')
>       assert config.needs_team_check("any-user") is False
E       AssertionError: assert True is False
E        +  where True = needs_team_check('any-user')
E        +    where needs_team_check = <stampbot.config.RepoConfig object at 0x7fcfb836ca10>.needs_team_check

tests/test_config.py:595: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_no_teams_configured - AssertionError: assert True is False
 +  where True = needs_team_check('any-user')
 +    where needs_team_check = <stampbot.config.RepoConfig object at 0x7fcfb836ca10>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 66 passed, 3 deselected, 1 warning in 1.36s
operator: core/AddNot, occurrence: 23, definition_name: needs_team_check
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -370,7 +370,7 @@
         """
         if not self.allowed_teams:
             return False
-        if self.allowed_users and pr_author in self.allowed_users:
+        if not self.allowed_users and pr_author in self.allowed_users:
             return False
         return True
 
...................................................................F
=================================== FAILURES ===================================
_________________ test_needs_team_check_user_in_allowed_users __________________

    def test_needs_team_check_user_in_allowed_users():
        """Test needs_team_check returns False when user is in allowed_users."""
        toml_content = """
    allowed_users = ["special-user"]
    allowed_teams = ["my-org/release-team"]
    """
        config = RepoConfig.from_toml(toml_content)
>       assert config.needs_team_check("special-user") is False
E       AssertionError: assert True is False
E        +  where True = needs_team_check('special-user')
E        +    where needs_team_check = <stampbot.config.RepoConfig object at 0x7c520bc78a10>.needs_team_check

tests/test_config.py:605: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_user_in_allowed_users - AssertionError: assert True is False
 +  where True = needs_team_check('special-user')
 +    where needs_team_check = <stampbot.config.RepoConfig object at 0x7c520bc78a10>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 67 passed, 3 deselected, 1 warning in 1.35s
operator: core/AddNot, occurrence: 24, definition_name: _get_defaults
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -386,7 +386,7 @@
         defaults = REPO_CONFIG_DEFAULTS.copy()
 
         # Allow overriding defaults via settings.toml [defaults] section
-        if settings.get("defaults"):
+        if not settings.get("defaults"):
             settings_defaults = settings.defaults
             for key in defaults:
                 if hasattr(settings_defaults, key):
................F
=================================== FAILURES ===================================
_ test_repo_config_default_or_config_error_fails_closed[approve_commands-approve-must be a list of strings] _

setting = 'approve_commands', value = 'approve'
message = 'must be a list of strings'

    @pytest.mark.parametrize(
        ("setting", "value", "message"),
        [
            ("approve_commands", "approve", "must be a list of strings"),
            ("unapprove_commands", ["unapprove", 1], "must contain only strings"),
            (
                "chatops_required_permission",
                "owner",
                "Invalid chatops_required_permission",
            ),
            (
                "chatops_required_permission",
                ["maintain"],
                "Invalid chatops_required_permission",
            ),
        ],
    )
    def test_repo_config_default_or_config_error_fails_closed(setting, value, message):
        """Test invalid service defaults produce a non-authorizing config."""
        service_defaults = MagicMock(spec=[setting])
        setattr(service_defaults, setting, value)
    
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = service_defaults
            mock_settings.defaults = service_defaults
    
            config = RepoConfig.default_or_config_error()
    
>       assert config.config_error is not None
E       assert None is not None
E        +  where None = <stampbot.config.RepoConfig object at 0x745afe7ad7f0>.config_error

tests/test_config.py:109: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_default_or_config_error_fails_closed[approve_commands-approve-must be a list of strings] - assert None is not None
 +  where None = <stampbot.config.RepoConfig object at 0x745afe7ad7f0>.config_error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 16 passed, 3 deselected, 1 warning in 1.31s
operator: core/AddNot, occurrence: 25, definition_name: _get_defaults
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -389,7 +389,7 @@
         if settings.get("defaults"):
             settings_defaults = settings.defaults
             for key in defaults:
-                if hasattr(settings_defaults, key):
+                if not hasattr(settings_defaults, key):
                     defaults[key] = getattr(settings_defaults, key)
 
         return defaults
................F
=================================== FAILURES ===================================
_ test_repo_config_default_or_config_error_fails_closed[approve_commands-approve-must be a list of strings] _

setting = 'approve_commands', value = 'approve'
message = 'must be a list of strings'

    @pytest.mark.parametrize(
        ("setting", "value", "message"),
        [
            ("approve_commands", "approve", "must be a list of strings"),
            ("unapprove_commands", ["unapprove", 1], "must contain only strings"),
            (
                "chatops_required_permission",
                "owner",
                "Invalid chatops_required_permission",
            ),
            (
                "chatops_required_permission",
                ["maintain"],
                "Invalid chatops_required_permission",
            ),
        ],
    )
    def test_repo_config_default_or_config_error_fails_closed(setting, value, message):
        """Test invalid service defaults produce a non-authorizing config."""
        service_defaults = MagicMock(spec=[setting])
        setattr(service_defaults, setting, value)
    
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = service_defaults
            mock_settings.defaults = service_defaults
    
>           config = RepoConfig.default_or_config_error()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:107: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:476: in default_or_config_error
    return cls.default()
           ^^^^^^^^^^^^^
stampbot/config.py:453: in default
    defaults = cls._get_defaults()
               ^^^^^^^^^^^^^^^^^^^
stampbot/config.py:393: in _get_defaults
    defaults[key] = getattr(settings_defaults, key)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='settings.get()' id='125193173367312'>
name = 'approval_labels'

    def __getattr__(self, name):
        if name in {'_mock_methods', '_mock_unsafe'}:
            raise AttributeError(name)
        elif self._mock_methods is not None:
            if name not in self._mock_methods or name in _all_magics:
>               raise AttributeError("Mock object has no attribute %r" % name)
E               AttributeError: Mock object has no attribute 'approval_labels'

/usr/lib/python3.14/unittest/mock.py:697: AttributeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_default_or_config_error_fails_closed[approve_commands-approve-must be a list of strings] - AttributeError: Mock object has no attribute 'approval_labels'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 16 passed, 3 deselected, 1 warning in 1.31s
operator: core/AddNot, occurrence: 26, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -415,7 +415,7 @@
 
         # Parse the repo-specific TOML
         repo_settings: dict[str, Any] = {}
-        if toml_content:
+        if not toml_content:
             try:
                 repo_settings = toml.loads(toml_content)
             except Exception as e:
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
        config = RepoConfig.from_toml(toml_content)
>       assert "test" in config.approval_labels
E       AssertionError: assert 'test' in ['autoapprove', 'stamp']
E        +  where ['autoapprove', 'stamp'] = <stampbot.config.RepoConfig object at 0x7fd1a72c3a10>.approval_labels

tests/test_config.py:32: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - AssertionError: assert 'test' in ['autoapprove', 'stamp']
 +  where ['autoapprove', 'stamp'] = <stampbot.config.RepoConfig object at 0x7fd1a72c3a10>.approval_labels
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.30s
operator: core/ReplaceTrueWithFalse, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -22,7 +22,7 @@
 # These can be overridden in settings.toml or per-repo stampbot.toml
 REPO_CONFIG_DEFAULTS = {
     "approval_labels": ["autoapprove", "stamp"],
-    "auto_approve_on_label": True,
+    "auto_approve_on_label": False,
     "reapprove": False,
     "chatops_enabled": True,
     "chatops_required_permission": "maintain",
................................F
=================================== FAILURES ===================================
_____________ test_repo_config_get_defaults_with_settings_override _____________

    def test_repo_config_get_defaults_with_settings_override():
        """Test _get_defaults respects settings.toml overrides."""
        with patch("stampbot.config.settings") as mock_settings:
            # Create a mock defaults object with only specific attributes
            # Using spec=[] means hasattr will return False for most attributes
            mock_defaults = MagicMock(
                spec=["approval_labels", "chatops_enabled", "chatops_required_permission"]
            )
            mock_defaults.approval_labels = ["custom-label"]
            mock_defaults.chatops_enabled = False
            mock_defaults.chatops_required_permission = "write"
    
            mock_settings.get.return_value = mock_defaults
            mock_settings.defaults = mock_defaults
    
            defaults = RepoConfig._get_defaults()
    
            # Should have the overridden values
            assert defaults["approval_labels"] == ["custom-label"]
            assert defaults["chatops_enabled"] is False
            assert defaults["chatops_required_permission"] == "write"
            # Non-overridden values should use REPO_CONFIG_DEFAULTS
>           assert defaults["auto_approve_on_label"] is True
E           assert False is True

tests/test_config.py:243: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_get_defaults_with_settings_override - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 32 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceTrueWithFalse, occurrence: 1, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -24,7 +24,7 @@
     "approval_labels": ["autoapprove", "stamp"],
     "auto_approve_on_label": True,
     "reapprove": False,
-    "chatops_enabled": True,
+    "chatops_enabled": False,
     "chatops_required_permission": "maintain",
     "approve_commands": ["approve", "stamp"],
     "unapprove_commands": ["unapprove", "unstamp"],
..................................F
=================================== FAILURES ===================================
________________ test_repo_config_get_defaults_partial_override ________________

    def test_repo_config_get_defaults_partial_override():
        """Test _get_defaults with partial settings override."""
        with patch("stampbot.config.settings") as mock_settings:
            # Create a mock defaults object with only some attributes
            mock_defaults = MagicMock(spec=[])  # Empty spec means no attributes
            # But we can still check hasattr behavior by setting one attribute
            mock_defaults.approval_labels = ["override-only-this"]
    
            mock_settings.get.return_value = mock_defaults
            mock_settings.defaults = mock_defaults
    
            defaults = RepoConfig._get_defaults()
    
            # The overridden value
            assert defaults["approval_labels"] == ["override-only-this"]
            # Non-overridden values should use REPO_CONFIG_DEFAULTS
            assert defaults["auto_approve_on_label"] is True
>           assert defaults["chatops_enabled"] is True
E           assert False is True

tests/test_config.py:277: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_get_defaults_partial_override - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 34 passed, 3 deselected, 1 warning in 1.30s
operator: core/ReplaceTrueWithFalse, occurrence: 2, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -49,7 +49,7 @@
     envvar_prefix="STAMPBOT",
     settings_files=["settings.toml", ".secrets.toml"],
     environments=False,  # We don't use [development]/[production] sections
-    load_dotenv=True,
+    load_dotenv=False,
 )
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceTrueWithFalse, occurrence: 3, definition_name: fail_closed
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -263,7 +263,7 @@
         """
         return cls(
             approval_labels=["autoapprove", "stamp"],
-            auto_approve_on_label=True,
+            auto_approve_on_label=False,
             reapprove=False,
             chatops_enabled=True,
             chatops_required_permission="maintain",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceTrueWithFalse, occurrence: 4, definition_name: fail_closed
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -265,7 +265,7 @@
             approval_labels=["autoapprove", "stamp"],
             auto_approve_on_label=True,
             reapprove=False,
-            chatops_enabled=True,
+            chatops_enabled=False,
             chatops_required_permission="maintain",
             approve_commands=["approve", "stamp"],
             unapprove_commands=["unapprove", "unstamp"],
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceTrueWithFalse, occurrence: 5, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -320,7 +320,7 @@
                     if pattern.search(
                         pr_title,
                         timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
-                        concurrent=True,
+                        concurrent=False,
                     ):
                         break
                 except TimeoutError:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceTrueWithFalse, occurrence: 6, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -339,7 +339,7 @@
         if self.allowed_users or self.allowed_teams:
             # User is allowed if they're in allowed_users
             if pr_author in self.allowed_users:
-                return True, None
+                return False, None
 
             # Or if they're a member of an allowed team
             if self.allowed_teams and author_team_slugs:
..........................................................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_user _______________________

    def test_is_pr_eligible_allowed_user():
        """Test is_pr_eligible returns True when author is in allowed_users."""
        config = RepoConfig.from_toml('allowed_users = ["dependabot[bot]", "renovate[bot]"]')
        is_eligible, reason = config.is_pr_eligible([], "Update deps", "dependabot[bot]")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:505: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_user - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 58 passed, 3 deselected, 1 warning in 1.35s
operator: core/ReplaceTrueWithFalse, occurrence: 7, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -347,7 +347,7 @@
                     # Support both "org/team" and "team" formats
                     team_slug = team.split("/")[-1] if "/" in team else team
                     if team_slug in author_team_slugs:
-                        return True, None
+                        return False, None
 
             # Neither user nor team matched
             if self.allowed_users and self.allowed_teams:
............................................................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________

    def test_is_pr_eligible_allowed_team():
        """Test is_pr_eligible returns True when author is in an allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "team-member", author_team_slugs=["release-team"]
        )
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:523: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected, 1 warning in 1.36s
operator: core/ReplaceTrueWithFalse, occurrence: 8, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -357,7 +357,7 @@
             else:
                 return False, "PR author not a member of any allowed team"
 
-        return True, None
+        return False, None
 
     def needs_team_check(self, pr_author: str) -> bool:
         """Check if team membership verification is needed for this author.
...........................................F
=================================== FAILURES ===================================
________________________ test_is_pr_eligible_no_filters ________________________

    def test_is_pr_eligible_no_filters():
        """Test is_pr_eligible returns True when no filters configured."""
        config = RepoConfig.default()
        is_eligible, reason = config.is_pr_eligible(["some-label"], "Some PR title", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:353: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_no_filters - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 43 passed, 3 deselected, 1 warning in 1.30s
operator: core/ReplaceTrueWithFalse, occurrence: 9, definition_name: needs_team_check
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -372,7 +372,7 @@
             return False
         if self.allowed_users and pr_author in self.allowed_users:
             return False
-        return True
+        return False
 
     @classmethod
     def _get_defaults(cls) -> dict[str, Any]:
....................................................................F
=================================== FAILURES ===================================
_______________ test_needs_team_check_user_not_in_allowed_users ________________

    def test_needs_team_check_user_not_in_allowed_users():
        """Test needs_team_check returns True when user not in allowed_users but teams configured."""
        toml_content = """
    allowed_users = ["special-user"]
    allowed_teams = ["my-org/release-team"]
    """
        config = RepoConfig.from_toml(toml_content)
>       assert config.needs_team_check("other-user") is True
E       AssertionError: assert False is True
E        +  where False = needs_team_check('other-user')
E        +    where needs_team_check = <stampbot.config.RepoConfig object at 0x7170d8300a10>.needs_team_check

tests/test_config.py:615: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_user_not_in_allowed_users - AssertionError: assert False is True
 +  where False = needs_team_check('other-user')
 +    where needs_team_check = <stampbot.config.RepoConfig object at 0x7170d8300a10>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 68 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceTrueWithFalse, occurrence: 10, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -428,7 +428,7 @@
 
         return cls(
             approval_labels=merged.get("approval_labels", []),
-            auto_approve_on_label=merged.get("auto_approve_on_label", True),
+            auto_approve_on_label=merged.get("auto_approve_on_label", False),
             reapprove=merged.get("reapprove", False),
             chatops_enabled=merged.get("chatops_enabled", True),
             chatops_required_permission=merged.get(
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceTrueWithFalse, occurrence: 11, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -430,7 +430,7 @@
             approval_labels=merged.get("approval_labels", []),
             auto_approve_on_label=merged.get("auto_approve_on_label", True),
             reapprove=merged.get("reapprove", False),
-            chatops_enabled=merged.get("chatops_enabled", True),
+            chatops_enabled=merged.get("chatops_enabled", False),
             chatops_required_permission=merged.get(
                 "chatops_required_permission",
                 "maintain",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/ReplaceFalseWithTrue, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -23,7 +23,7 @@
 REPO_CONFIG_DEFAULTS = {
     "approval_labels": ["autoapprove", "stamp"],
     "auto_approve_on_label": True,
-    "reapprove": False,
+    "reapprove": True,
     "chatops_enabled": True,
     "chatops_required_permission": "maintain",
     "approve_commands": ["approve", "stamp"],
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/ReplaceFalseWithTrue, occurrence: 1, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -48,7 +48,7 @@
 settings = Dynaconf(
     envvar_prefix="STAMPBOT",
     settings_files=["settings.toml", ".secrets.toml"],
-    environments=False,  # We don't use [development]/[production] sections
+    environments=True,  # We don't use [development]/[production] sections
     load_dotenv=True,
 )
 
==================================== ERRORS ====================================
_________________ ERROR collecting tests/test_http_metrics.py __________________
tests/test_http_metrics.py:11: in <module>
    from stampbot.main import UNMATCHED_ENDPOINT, _metric_endpoint_label, metrics_middleware
stampbot/main.py:51: in <module>
    configure_logging()
stampbot/logger.py:57: in configure_logging
    log_level_int = logging.getLevelName(settings.log_level)
                                         ^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:98: in __getattr__
    return getattr(self._wrapped, name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/dynaconf/base.py:372: in __getattr__
    raise AttributeError(key)
E   AttributeError: log_level
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
ERROR tests/test_http_metrics.py - AttributeError: log_level
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 warning, 1 error in 1.04s
operator: core/ReplaceFalseWithTrue, occurrence: 2, definition_name: fail_closed
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -264,7 +264,7 @@
         return cls(
             approval_labels=["autoapprove", "stamp"],
             auto_approve_on_label=True,
-            reapprove=False,
+            reapprove=True,
             chatops_enabled=True,
             chatops_required_permission="maintain",
             approve_commands=["approve", "stamp"],
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceFalseWithTrue, occurrence: 3, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -301,7 +301,7 @@
         if self.required_labels:
             if not any(label in self.required_labels for label in pr_labels):
                 return (
-                    False,
+                    True,
                     f"PR missing required label (one of: {', '.join(self.required_labels)})",
                 )
 
.............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_required_label_missing __________________

    def test_is_pr_eligible_required_label_missing():
        """Test is_pr_eligible returns False when required label is missing."""
        config = RepoConfig.from_toml('required_labels = ["dependencies", "automated"]')
        is_eligible, reason = config.is_pr_eligible(["other-label"], "PR title", "someuser")
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:369: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_required_label_missing - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 45 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceFalseWithTrue, occurrence: 4, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -308,7 +308,7 @@
         # Check required title patterns filter
         if self._compiled_title_patterns:
             if not isinstance(pr_title, str):
-                return False, "PR title is not valid text"
+                return True, "PR title is not valid text"
             if len(pr_title) > MAX_PR_TITLE_LENGTH:
                 return (
                     False,
....................................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_rejects_non_text_title __________________

    def test_is_pr_eligible_rejects_non_text_title():
        """Test malformed webhook title input fails closed."""
        config = RepoConfig.from_toml('required_title_patterns = [".*"]')
    
        is_eligible, reason = config.is_pr_eligible([], None, "someuser")  # type: ignore[arg-type]
    
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:437: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_rejects_non_text_title - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 52 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceFalseWithTrue, occurrence: 5, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -311,7 +311,7 @@
                 return False, "PR title is not valid text"
             if len(pr_title) > MAX_PR_TITLE_LENGTH:
                 return (
-                    False,
+                    True,
                     f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                 )
 
...................................................F
=================================== FAILURES ===================================
__________ test_is_pr_eligible_rejects_overlong_title_before_matching __________

    def test_is_pr_eligible_rejects_overlong_title_before_matching():
        """Test webhook title input is bounded before regex evaluation."""
        config = RepoConfig.from_toml('required_title_patterns = [".*"]')
        config._compiled_title_patterns[0] = MagicMock()
    
        is_eligible, reason = config.is_pr_eligible([], "a" * (MAX_PR_TITLE_LENGTH + 1), "someuser")
    
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:426: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_rejects_overlong_title_before_matching - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 51 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceFalseWithTrue, occurrence: 6, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -326,7 +326,7 @@
                 except TimeoutError:
                     timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
                     return (
-                        False,
+                        True,
                         "PR title pattern evaluation could not complete within "
                         f"the {timeout_ms} ms safety limit",
                     )
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
        is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )
    
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:449: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.36s
operator: core/ReplaceFalseWithTrue, occurrence: 7, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -331,7 +331,7 @@
                         f"the {timeout_ms} ms safety limit",
                     )
                 except regex.error:
-                    return False, "PR title pattern evaluation failed safely"
+                    return True, "PR title pattern evaluation failed safely"
             else:
                 return False, "PR title does not match any required pattern"
 
......................................................F
=================================== FAILURES ===================================
______________ test_is_pr_eligible_regex_engine_failure_is_closed ______________

    def test_is_pr_eligible_regex_engine_failure_is_closed():
        """Test a regex engine failure cannot approve a pull request."""
        config = RepoConfig.from_toml('required_title_patterns = ["^fix:"]')
        config._compiled_title_patterns[0] = MagicMock()
        config._compiled_title_patterns[0].search.side_effect = regex.error("engine failure")
    
        is_eligible, reason = config.is_pr_eligible([], "fix: dependency", "someuser")
    
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:461: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_regex_engine_failure_is_closed - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 54 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceFalseWithTrue, occurrence: 8, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -333,7 +333,7 @@
                 except regex.error:
                     return False, "PR title pattern evaluation failed safely"
             else:
-                return False, "PR title does not match any required pattern"
+                return True, "PR title does not match any required pattern"
 
         # Check allowed users/teams filter (if either is configured)
         if self.allowed_users or self.allowed_teams:
................................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_no_match __________________

    def test_is_pr_eligible_title_pattern_no_match():
        """Test is_pr_eligible returns False when title doesn't match any pattern."""
        config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
        is_eligible, reason = config.is_pr_eligible([], "feat: add new feature", "someuser")
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:394: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_no_match - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 48 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceFalseWithTrue, occurrence: 9, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -351,7 +351,7 @@
 
             # Neither user nor team matched
             if self.allowed_users and self.allowed_teams:
-                return False, "PR author not in allowed users or teams"
+                return True, "PR author not in allowed users or teams"
             elif self.allowed_users:
                 return False, f"PR author not in allowed users: {', '.join(self.allowed_users)}"
             else:
...............................................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_neither_user_nor_team ___________________

    def test_is_pr_eligible_neither_user_nor_team():
        """Test is_pr_eligible returns False when neither user nor team matches."""
        toml_content = """
    allowed_users = ["special-user"]
    allowed_teams = ["my-org/release-team"]
    """
        config = RepoConfig.from_toml(toml_content)
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "random-user", author_team_slugs=["other-team"]
        )
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:560: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_neither_user_nor_team - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 63 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceFalseWithTrue, occurrence: 10, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -353,7 +353,7 @@
             if self.allowed_users and self.allowed_teams:
                 return False, "PR author not in allowed users or teams"
             elif self.allowed_users:
-                return False, f"PR author not in allowed users: {', '.join(self.allowed_users)}"
+                return True, f"PR author not in allowed users: {', '.join(self.allowed_users)}"
             else:
                 return False, "PR author not a member of any allowed team"
 
...........................................................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_user_not_allowed _____________________

    def test_is_pr_eligible_user_not_allowed():
        """Test is_pr_eligible returns False when author is not in allowed_users."""
        config = RepoConfig.from_toml('allowed_users = ["dependabot[bot]", "renovate[bot]"]')
        is_eligible, reason = config.is_pr_eligible([], "Update deps", "random-user")
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:513: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_user_not_allowed - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 59 passed, 3 deselected, 1 warning in 1.35s
operator: core/ReplaceFalseWithTrue, occurrence: 11, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -355,7 +355,7 @@
             elif self.allowed_users:
                 return False, f"PR author not in allowed users: {', '.join(self.allowed_users)}"
             else:
-                return False, "PR author not a member of any allowed team"
+                return True, "PR author not a member of any allowed team"
 
         return True, None
 
.............................................................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_team_not_matched _____________________

    def test_is_pr_eligible_team_not_matched():
        """Test is_pr_eligible returns False when author is not in any allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "non-member", author_team_slugs=["other-team"]
        )
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:533: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_team_not_matched - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 61 passed, 3 deselected, 1 warning in 1.36s
operator: core/ReplaceFalseWithTrue, occurrence: 12, definition_name: needs_team_check
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -369,7 +369,7 @@
             True if team check is needed (author not in allowed_users but teams configured)
         """
         if not self.allowed_teams:
-            return False
+            return True
         if self.allowed_users and pr_author in self.allowed_users:
             return False
         return True
..................................................................F
=================================== FAILURES ===================================
__________________ test_needs_team_check_no_teams_configured ___________________

    def test_needs_team_check_no_teams_configured():
        """Test needs_team_check returns False when no teams configured."""
        config = RepoConfig.from_toml('allowed_users = ["some-user"]')
>       assert config.needs_team_check("any-user") is False
E       AssertionError: assert True is False
E        +  where True = needs_team_check('any-user')
E        +    where needs_team_check = <stampbot.config.RepoConfig object at 0x742baa348a10>.needs_team_check

tests/test_config.py:595: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_no_teams_configured - AssertionError: assert True is False
 +  where True = needs_team_check('any-user')
 +    where needs_team_check = <stampbot.config.RepoConfig object at 0x742baa348a10>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 66 passed, 3 deselected, 1 warning in 1.35s
operator: core/ReplaceFalseWithTrue, occurrence: 13, definition_name: needs_team_check
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -371,7 +371,7 @@
         if not self.allowed_teams:
             return False
         if self.allowed_users and pr_author in self.allowed_users:
-            return False
+            return True
         return True
 
     @classmethod
...................................................................F
=================================== FAILURES ===================================
_________________ test_needs_team_check_user_in_allowed_users __________________

    def test_needs_team_check_user_in_allowed_users():
        """Test needs_team_check returns False when user is in allowed_users."""
        toml_content = """
    allowed_users = ["special-user"]
    allowed_teams = ["my-org/release-team"]
    """
        config = RepoConfig.from_toml(toml_content)
>       assert config.needs_team_check("special-user") is False
E       AssertionError: assert True is False
E        +  where True = needs_team_check('special-user')
E        +    where needs_team_check = <stampbot.config.RepoConfig object at 0x7883dce10a10>.needs_team_check

tests/test_config.py:605: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_user_in_allowed_users - AssertionError: assert True is False
 +  where True = needs_team_check('special-user')
 +    where needs_team_check = <stampbot.config.RepoConfig object at 0x7883dce10a10>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 67 passed, 3 deselected, 1 warning in 1.32s
operator: core/ReplaceFalseWithTrue, occurrence: 14, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -429,7 +429,7 @@
         return cls(
             approval_labels=merged.get("approval_labels", []),
             auto_approve_on_label=merged.get("auto_approve_on_label", True),
-            reapprove=merged.get("reapprove", False),
+            reapprove=merged.get("reapprove", True),
             chatops_enabled=merged.get("chatops_enabled", True),
             chatops_required_permission=merged.get(
                 "chatops_required_permission",
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.42s
operator: core/ReplaceAndWithOr, occurrence: 0, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -84,7 +84,7 @@
     # Dynaconf parses environment values, so ``false`` arrives as a bool and
     # ``1.9`` as a float. Neither is an integer setting, and int() would accept
     # both silently.
-    if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
+    if isinstance(raw, bool) or (isinstance(raw, float) or not raw.is_integer()):
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
     try:
         seconds = int(raw)
......................................................................F
=================================== FAILURES ===================================
___________ test_repo_config_cache_seconds_accepts_integers[120-120] ___________

raw = '120', expected = 120

    @pytest.mark.parametrize(
        ("raw", "expected"),
        [(300, 300), ("120", 120), (0, 0), (5.0, 5)],
    )
    def test_repo_config_cache_seconds_accepts_integers(raw, expected):
        """Test the cache lifetime accepts integer-valued settings."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = raw
    
>           assert repo_config_cache_seconds() == expected
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:627: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def repo_config_cache_seconds() -> int:
        """Return how long a repository's parsed policy stays cached, in seconds.
    
        Returns:
            A non-negative integer. Zero disables the cache.
    
        Raises:
            ValueError: If ``repo_config_cache_seconds`` is not a non-negative integer.
        """
        raw = settings.get("repo_config_cache_seconds", DEFAULT_REPO_CONFIG_CACHE_SECONDS)
        # Dynaconf parses environment values, so ``false`` arrives as a bool and
        # ``1.9`` as a float. Neither is an integer setting, and int() would accept
        # both silently.
>       if isinstance(raw, bool) or (isinstance(raw, float) or not raw.is_integer()):
                                                                   ^^^^^^^^^^^^^^
E       AttributeError: 'str' object has no attribute 'is_integer'

stampbot/config.py:87: AttributeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_cache_seconds_accepts_integers[120-120] - AttributeError: 'str' object has no attribute 'is_integer'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 70 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceAndWithOr, occurrence: 1, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -342,7 +342,7 @@
                 return True, None
 
             # Or if they're a member of an allowed team
-            if self.allowed_teams and author_team_slugs:
+            if self.allowed_teams or author_team_slugs:
                 for team in self.allowed_teams:
                     # Support both "org/team" and "team" formats
                     team_slug = team.split("/")[-1] if "/" in team else team
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.43s
operator: core/ReplaceAndWithOr, occurrence: 2, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -350,7 +350,7 @@
                         return True, None
 
             # Neither user nor team matched
-            if self.allowed_users and self.allowed_teams:
+            if self.allowed_users or self.allowed_teams:
                 return False, "PR author not in allowed users or teams"
             elif self.allowed_users:
                 return False, f"PR author not in allowed users: {', '.join(self.allowed_users)}"
.............................................................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_team_not_matched _____________________

    def test_is_pr_eligible_team_not_matched():
        """Test is_pr_eligible returns False when author is not in any allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "non-member", author_team_slugs=["other-team"]
        )
        assert is_eligible is False
>       assert "not a member of any allowed team" in reason
E       AssertionError: assert 'not a member of any allowed team' in 'PR author not in allowed users or teams'

tests/test_config.py:534: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_team_not_matched - AssertionError: assert 'not a member of any allowed team' in 'PR author not in allowed users or teams'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 61 passed, 3 deselected, 1 warning in 1.35s
operator: core/ReplaceAndWithOr, occurrence: 3, definition_name: needs_team_check
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -370,7 +370,7 @@
         """
         if not self.allowed_teams:
             return False
-        if self.allowed_users and pr_author in self.allowed_users:
+        if self.allowed_users or pr_author in self.allowed_users:
             return False
         return True
 
....................................................................F
=================================== FAILURES ===================================
_______________ test_needs_team_check_user_not_in_allowed_users ________________

    def test_needs_team_check_user_not_in_allowed_users():
        """Test needs_team_check returns True when user not in allowed_users but teams configured."""
        toml_content = """
    allowed_users = ["special-user"]
    allowed_teams = ["my-org/release-team"]
    """
        config = RepoConfig.from_toml(toml_content)
>       assert config.needs_team_check("other-user") is True
E       AssertionError: assert False is True
E        +  where False = needs_team_check('other-user')
E        +    where needs_team_check = <stampbot.config.RepoConfig object at 0x712ae1728a10>.needs_team_check

tests/test_config.py:615: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_needs_team_check_user_not_in_allowed_users - AssertionError: assert False is True
 +  where False = needs_team_check('other-user')
 +    where needs_team_check = <stampbot.config.RepoConfig object at 0x712ae1728a10>.needs_team_check
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 68 passed, 3 deselected, 1 warning in 1.31s
operator: core/ReplaceOrWithAnd, occurrence: 0, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -84,7 +84,7 @@
     # Dynaconf parses environment values, so ``false`` arrives as a bool and
     # ``1.9`` as a float. Neither is an integer setting, and int() would accept
     # both silently.
-    if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
+    if isinstance(raw, bool) and (isinstance(raw, float) and not raw.is_integer()):
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
     try:
         seconds = int(raw)
........................................................................ [ 15%]
.......F
=================================== FAILURES ===================================
_________ test_repo_config_cache_seconds_rejects_invalid_values[True] __________

raw = True

    @pytest.mark.parametrize("raw", ["soon", None, -5, "-1", [300], True, False, 1.9, "1.9"])
    def test_repo_config_cache_seconds_rejects_invalid_values(raw):
        """Test a non-integer or negative lifetime fails at startup instead of guessing."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = raw
    
>           with pytest.raises(ValueError, match="repo_config_cache_seconds"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE ValueError

tests/test_config.py:644: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_cache_seconds_rejects_invalid_values[True] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 79 passed, 3 deselected, 1 warning in 1.34s
operator: core/ReplaceOrWithAnd, occurrence: 1, definition_name: __init__
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -161,7 +161,7 @@
             unapprove_commands,
             "unapprove_commands",
         )
-        self.required_labels = required_labels or []
+        self.required_labels = required_labels and []
         title_patterns = required_title_patterns if required_title_patterns is not None else []
         self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
         self.required_title_patterns = list(title_patterns)
...................................F
=================================== FAILURES ===================================
____________________ test_repo_config_with_required_labels _____________________

    def test_repo_config_with_required_labels():
        """Test parsing repository config with required labels."""
        toml_content = """
    required_labels = ["dependencies", "automated"]
    """
        config = RepoConfig.from_toml(toml_content)
>       assert config.required_labels == ["dependencies", "automated"]
E       AssertionError: assert [] == ['dependencies', 'automated']
E         
E         Right contains 2 more items, first extra item: 'dependencies'
E         
E         Full diff: (-: missing in left side, +: extra in left side)
E         + []
E         - [
E         -     'dependencies',
E         -     'automated',
E         - ]

tests/test_config.py:287: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_with_required_labels - AssertionError: assert [] == ['dependencies', 'automated']
  
  Right contains 2 more items, first extra item: 'dependencies'
  
  Full diff: (-: missing in left side, +: extra in left side)
  + []
  - [
  -     'dependencies',
  -     'automated',
  - ]
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 35 passed, 3 deselected, 1 warning in 1.32s
operator: core/ReplaceOrWithAnd, occurrence: 2, definition_name: __init__
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -165,7 +165,7 @@
         title_patterns = required_title_patterns if required_title_patterns is not None else []
         self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
         self.required_title_patterns = list(title_patterns)
-        self.allowed_users = allowed_users or []
+        self.allowed_users = allowed_users and []
         self.allowed_teams = allowed_teams or []
         self.config_error = config_error
 
...........................................................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_user_not_allowed _____________________

    def test_is_pr_eligible_user_not_allowed():
        """Test is_pr_eligible returns False when author is not in allowed_users."""
        config = RepoConfig.from_toml('allowed_users = ["dependabot[bot]", "renovate[bot]"]')
        is_eligible, reason = config.is_pr_eligible([], "Update deps", "random-user")
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:513: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_user_not_allowed - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 59 passed, 3 deselected, 1 warning in 1.33s
operator: core/ReplaceOrWithAnd, occurrence: 3, definition_name: __init__
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -166,7 +166,7 @@
         self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
         self.required_title_patterns = list(title_patterns)
         self.allowed_users = allowed_users or []
-        self.allowed_teams = allowed_teams or []
+        self.allowed_teams = allowed_teams and []
         self.config_error = config_error
 
     @staticmethod
.............................................................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_team_not_matched _____________________

    def test_is_pr_eligible_team_not_matched():
        """Test is_pr_eligible returns False when author is not in any allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "non-member", author_team_slugs=["other-team"]
        )
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:533: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_team_not_matched - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 61 passed, 3 deselected, 1 warning in 1.36s
operator: core/ReplaceOrWithAnd, occurrence: 4, definition_name: _validate_required_permission
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -202,7 +202,7 @@
         Raises:
             ValueError: If the permission is not one of GitHub's repository levels.
         """
-        if not isinstance(permission, str) or permission not in REPO_PERMISSION_LEVELS:
+        if not isinstance(permission, str) and permission not in REPO_PERMISSION_LEVELS:
             raise ValueError(
                 "Invalid chatops_required_permission: "
                 f"{permission}. "
..................F
=================================== FAILURES ===================================
_ test_repo_config_default_or_config_error_fails_closed[chatops_required_permission-owner-Invalid chatops_required_permission] _

setting = 'chatops_required_permission', value = 'owner'
message = 'Invalid chatops_required_permission'

    @pytest.mark.parametrize(
        ("setting", "value", "message"),
        [
            ("approve_commands", "approve", "must be a list of strings"),
            ("unapprove_commands", ["unapprove", 1], "must contain only strings"),
            (
                "chatops_required_permission",
                "owner",
                "Invalid chatops_required_permission",
            ),
            (
                "chatops_required_permission",
                ["maintain"],
                "Invalid chatops_required_permission",
            ),
        ],
    )
    def test_repo_config_default_or_config_error_fails_closed(setting, value, message):
        """Test invalid service defaults produce a non-authorizing config."""
        service_defaults = MagicMock(spec=[setting])
        setattr(service_defaults, setting, value)
    
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = service_defaults
            mock_settings.defaults = service_defaults
    
            config = RepoConfig.default_or_config_error()
    
>       assert config.config_error is not None
E       assert None is not None
E        +  where None = <stampbot.config.RepoConfig object at 0x7753e1ad9c50>.config_error

tests/test_config.py:109: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_default_or_config_error_fails_closed[chatops_required_permission-owner-Invalid chatops_required_permission] - assert None is not None
 +  where None = <stampbot.config.RepoConfig object at 0x7753e1ad9c50>.config_error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 18 passed, 3 deselected, 1 warning in 1.29s
operator: core/ReplaceOrWithAnd, occurrence: 5, definition_name: fail_closed
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -273,7 +273,7 @@
             required_title_patterns=[],
             allowed_users=[],
             allowed_teams=[],
-            config_error=message or "Invalid configuration",
+            config_error=message and "Invalid configuration",
         )
 
     def is_pr_eligible(
.............F
=================================== FAILURES ===================================
_________ test_repo_config_fail_closed_does_not_read_service_defaults __________

    def test_repo_config_fail_closed_does_not_read_service_defaults():
        """Test fail-closed config is independent of invalid service settings."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.side_effect = AssertionError("service settings must not be read")
    
            config = RepoConfig.fail_closed("invalid service configuration")
    
>       assert config.config_error == "invalid service configuration"
E       AssertionError: assert 'Invalid configuration' == 'invalid serv...configuration'
E         
E         - invalid service configuration
E         ? ^      --------
E         + Invalid configuration
E         ? ^

tests/test_config.py:61: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_fail_closed_does_not_read_service_defaults - AssertionError: assert 'Invalid configuration' == 'invalid serv...configuration'
  
  - invalid service configuration
  ? ^      --------
  + Invalid configuration
  ? ^
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 13 passed, 3 deselected, 1 warning in 1.36s
operator: core/ReplaceOrWithAnd, occurrence: 6, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -336,7 +336,7 @@
                 return False, "PR title does not match any required pattern"
 
         # Check allowed users/teams filter (if either is configured)
-        if self.allowed_users or self.allowed_teams:
+        if self.allowed_users and self.allowed_teams:
             # User is allowed if they're in allowed_users
             if pr_author in self.allowed_users:
                 return True, None
...........................................................F
=================================== FAILURES ===================================
_____________________ test_is_pr_eligible_user_not_allowed _____________________

    def test_is_pr_eligible_user_not_allowed():
        """Test is_pr_eligible returns False when author is not in allowed_users."""
        config = RepoConfig.from_toml('allowed_users = ["dependabot[bot]", "renovate[bot]"]')
        is_eligible, reason = config.is_pr_eligible([], "Update deps", "random-user")
>       assert is_eligible is False
E       assert True is False

tests/test_config.py:513: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_user_not_allowed - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 59 passed, 3 deselected, 1 warning in 1.36s
operator: core/ReplaceBreakWithContinue, occurrence: 0, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -322,7 +322,7 @@
                         timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                         concurrent=True,
                     ):
-                        break
+                        continue
                 except TimeoutError:
                     timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
                     return (
...............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_matches ___________________

    def test_is_pr_eligible_title_pattern_matches():
        """Test is_pr_eligible returns True when title matches pattern."""
        config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
        is_eligible, reason = config.is_pr_eligible([], "chore: update dependencies", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:386: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_matches - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected, 1 warning in 1.30s
operator: core/ExceptionReplacer, occurrence: 0, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -88,7 +88,7 @@
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
     try:
         seconds = int(raw)
-    except (TypeError, ValueError) as error:
+    except (CosmicRayTestingException, ValueError) as error:
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
     if seconds < 0:
         raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
........................................................................ [ 15%]
..F
=================================== FAILURES ===================================
_________ test_repo_config_cache_seconds_rejects_invalid_values[soon] __________

    def repo_config_cache_seconds() -> int:
        """Return how long a repository's parsed policy stays cached, in seconds.
    
        Returns:
            A non-negative integer. Zero disables the cache.
    
        Raises:
            ValueError: If ``repo_config_cache_seconds`` is not a non-negative integer.
        """
        raw = settings.get("repo_config_cache_seconds", DEFAULT_REPO_CONFIG_CACHE_SECONDS)
        # Dynaconf parses environment values, so ``false`` arrives as a bool and
        # ``1.9`` as a float. Neither is an integer setting, and int() would accept
        # both silently.
        if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
            raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
        try:
>           seconds = int(raw)
                      ^^^^^^^^
E           ValueError: invalid literal for int() with base 10: 'soon'

stampbot/config.py:90: ValueError

During handling of the above exception, another exception occurred:

raw = 'soon'

    @pytest.mark.parametrize("raw", ["soon", None, -5, "-1", [300], True, False, 1.9, "1.9"])
    def test_repo_config_cache_seconds_rejects_invalid_values(raw):
        """Test a non-integer or negative lifetime fails at startup instead of guessing."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = raw
    
            with pytest.raises(ValueError, match="repo_config_cache_seconds"):
>               repo_config_cache_seconds()

tests/test_config.py:645: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def repo_config_cache_seconds() -> int:
        """Return how long a repository's parsed policy stays cached, in seconds.
    
        Returns:
            A non-negative integer. Zero disables the cache.
    
        Raises:
            ValueError: If ``repo_config_cache_seconds`` is not a non-negative integer.
        """
        raw = settings.get("repo_config_cache_seconds", DEFAULT_REPO_CONFIG_CACHE_SECONDS)
        # Dynaconf parses environment values, so ``false`` arrives as a bool and
        # ``1.9`` as a float. Neither is an integer setting, and int() would accept
        # both silently.
        if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
            raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
        try:
            seconds = int(raw)
>       except (CosmicRayTestingException, ValueError) as error:
                ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/config.py:91: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_cache_seconds_rejects_invalid_values[soon] - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 74 passed, 3 deselected, 1 warning in 1.36s
operator: core/ExceptionReplacer, occurrence: 1, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -88,7 +88,7 @@
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
     try:
         seconds = int(raw)
-    except (TypeError, ValueError) as error:
+    except (TypeError, CosmicRayTestingException) as error:
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
     if seconds < 0:
         raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
........................................................................ [ 15%]
..F
=================================== FAILURES ===================================
_________ test_repo_config_cache_seconds_rejects_invalid_values[soon] __________

    def repo_config_cache_seconds() -> int:
        """Return how long a repository's parsed policy stays cached, in seconds.
    
        Returns:
            A non-negative integer. Zero disables the cache.
    
        Raises:
            ValueError: If ``repo_config_cache_seconds`` is not a non-negative integer.
        """
        raw = settings.get("repo_config_cache_seconds", DEFAULT_REPO_CONFIG_CACHE_SECONDS)
        # Dynaconf parses environment values, so ``false`` arrives as a bool and
        # ``1.9`` as a float. Neither is an integer setting, and int() would accept
        # both silently.
        if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
            raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
        try:
>           seconds = int(raw)
                      ^^^^^^^^
E           ValueError: invalid literal for int() with base 10: 'soon'

stampbot/config.py:90: ValueError

During handling of the above exception, another exception occurred:

raw = 'soon'

    @pytest.mark.parametrize("raw", ["soon", None, -5, "-1", [300], True, False, 1.9, "1.9"])
    def test_repo_config_cache_seconds_rejects_invalid_values(raw):
        """Test a non-integer or negative lifetime fails at startup instead of guessing."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = raw
    
            with pytest.raises(ValueError, match="repo_config_cache_seconds"):
>               repo_config_cache_seconds()

tests/test_config.py:645: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def repo_config_cache_seconds() -> int:
        """Return how long a repository's parsed policy stays cached, in seconds.
    
        Returns:
            A non-negative integer. Zero disables the cache.
    
        Raises:
            ValueError: If ``repo_config_cache_seconds`` is not a non-negative integer.
        """
        raw = settings.get("repo_config_cache_seconds", DEFAULT_REPO_CONFIG_CACHE_SECONDS)
        # Dynaconf parses environment values, so ``false`` arrives as a bool and
        # ``1.9`` as a float. Neither is an integer setting, and int() would accept
        # both silently.
        if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
            raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
        try:
            seconds = int(raw)
>       except (TypeError, CosmicRayTestingException) as error:
                           ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/config.py:91: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_cache_seconds_rejects_invalid_values[soon] - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 74 passed, 3 deselected, 1 warning in 1.34s
operator: core/ExceptionReplacer, occurrence: 2, definition_name: _compile_title_patterns
Traceback (most recent call last):
  File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/cosmic_ray/mutating.py", line 62, in mutate_and_test
    (previous_code, mutated_code) = stack.enter_context(
                                    ~~~~~~~~~~~~~~~~~~~^
        use_mutation(mutation.module_path, operator, mutation.occurrence)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/usr/lib/python3.14/contextlib.py", line 530, in enter_context
    result = _enter(cm)
  File "/usr/lib/python3.14/contextlib.py", line 141, in __enter__
    return next(self.gen)
  File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/cosmic_ray/mutating.py", line 114, in use_mutation
    original_code, mutated_code = apply_mutation(module_path, operator, occurrence)
                                  ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/cosmic_ray/mutating.py", line 130, in apply_mutation
    return MutationVisitor.mutate_path(module_path, operator, occurrence)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/cosmic_ray/mutating.py", line 183, in mutate_path
    mutated_code = cls.mutate_code(original_code, operator, occurrence)
  File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/cosmic_ray/mutating.py", line 163, in mutate_code
    mutated_ast = visitor.walk(ast)
  File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/cosmic_ray/ast/__init__.py", line 28, in walk
    node.children = [child for child in walked if child is not None]
                                        ^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/cosmic_ray/ast/__init__.py", line 28, in walk
    node.children = [child for child in walked if child is not None]
                                        ^^^^^^
  File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/cosmic_ray/ast/__init__.py", line 28, in walk
    node.children = [child for child in walked if child is not None]
                                        ^^^^^^
  [Previous line repeated 6 more times]
  File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/cosmic_ray/ast/__init__.py", line 21, in walk
    node = self.visit(node)
  File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/cosmic_ray/mutating.py", line 209, in visit
    node = self.operator.mutate(node, index)
  File "/home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/cosmic_ray/operators/exception_replacer.py", line 25, in mutate
    name_nodes[index].value = CosmicRayTestingException.__name__
    ^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'PythonNode' object has no attribute 'value' and no __dict__ for setting new attributes
operator: core/ExceptionReplacer, occurrence: 3, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -242,7 +242,7 @@
                 )
             try:
                 compiled_patterns.append(regex.compile(pattern, regex.VERSION0))
-            except (regex.error, OverflowError) as e:
+            except (regex.error, CosmicRayTestingException) as e:
                 raise ValueError(f"Invalid regex pattern '{pattern}': {e}") from e
 
         return compiled_patterns
.....................................F
=================================== FAILURES ===================================
____________________ test_repo_config_invalid_regex_pattern ____________________

patterns = ['[invalid']

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
            raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
    
        compiled_patterns = []
        for pattern in patterns:
            if not isinstance(pattern, str):
                raise ValueError("required_title_patterns must contain only strings")
            if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
                raise ValueError(
                    "required_title_patterns contains a pattern longer than "
                    f"{MAX_TITLE_PATTERN_LENGTH} characters"
                )
            try:
>               compiled_patterns.append(regex.compile(pattern, regex.VERSION0))
                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/config.py:244: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/regex/_main.py:363: in compile
    return _compile(pattern, flags, ignore_unused, kwargs, cache_pattern)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

pattern = '[invalid', flags = regex.V0, ignore_unused = False, kwargs = {}
cache_it = True

    def _compile(pattern, flags, ignore_unused, kwargs, cache_it):
        "Compiles a regular expression to a PatternObject."
    
        global DEFAULT_VERSION
        try:
            from regex import DEFAULT_VERSION
        except ImportError:
            pass
    
        # We won't bother to cache the pattern if we're debugging.
        if (flags & DEBUG) != 0:
            cache_it = False
    
        # What locale is this pattern using?
        locale_key = (type(pattern), pattern)
        if _locale_sensitive.get(locale_key, True) or (flags & LOCALE) != 0:
            # This pattern is, or might be, locale-sensitive.
            pattern_locale = _getpreferredencoding(False)
        else:
            # This pattern is definitely not locale-sensitive.
            pattern_locale = None
    
        def complain_unused_args():
            if ignore_unused:
                return
    
            # Complain about any unused keyword arguments, possibly resulting from a typo.
            unused_kwargs = set(kwargs) - {k for k, v in args_needed}
            if unused_kwargs:
                any_one = next(iter(unused_kwargs))
                raise ValueError('unused keyword argument {!a}'.format(any_one))
    
        if cache_it:
            try:
                # Do we know what keyword arguments are needed?
                args_key = pattern, type(pattern), flags
                args_needed = _named_args[args_key]
    
                # Are we being provided with its required keyword arguments?
                args_supplied = set()
                if args_needed:
                    for k, v in args_needed:
                        try:
                            args_supplied.add((k, frozenset(kwargs[k])))
                        except KeyError:
                            raise error("missing named list: {!r}".format(k))
    
                complain_unused_args()
    
                args_supplied = frozenset(args_supplied)
    
                # Have we already seen this regular expression and named list?
                pattern_key = (pattern, type(pattern), flags, args_supplied,
                  DEFAULT_VERSION, pattern_locale)
                return _cache[pattern_key]
            except KeyError:
                # It's a new pattern, or new named list for a known pattern.
                pass
    
        # Guess the encoding from the class of the pattern string.
        if isinstance(pattern, str):
            guess_encoding = UNICODE
        elif isinstance(pattern, bytes):
            guess_encoding = ASCII
        elif isinstance(pattern, Pattern):
            if flags:
                raise ValueError("cannot process flags argument with a compiled pattern")
    
            return pattern
        else:
            raise TypeError("first argument must be a string or compiled pattern")
    
        # Set the default version in the core code in case it has been changed.
        _regex_core.DEFAULT_VERSION = DEFAULT_VERSION
    
        global_flags = flags
    
        while True:
            caught_exception = None
            try:
                source = _Source(pattern)
                info = _Info(global_flags, source.char_type, kwargs)
                info.guess_encoding = guess_encoding
                source.ignore_space = bool(info.flags & VERBOSE)
                parsed = _parse_pattern(source, info)
                break
            except _UnscopedFlagSet:
                # Remember the global flags for the next attempt.
                global_flags = info.global_flags
            except error as e:
                caught_exception = e
    
            if caught_exception:
>               raise error(caught_exception.msg, caught_exception.pattern,
                  caught_exception.pos)
E               regex._regex_core.error: unterminated character set at position 8

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/regex/_main.py:553: error

During handling of the above exception, another exception occurred:

    def test_repo_config_invalid_regex_pattern():
        """Test invalid regex pattern raises a ValueError."""
        toml_content = """
    required_title_patterns = ["[invalid"]
    """
        with pytest.raises(ValueError, match="Invalid regex pattern"):
>           RepoConfig.from_toml(toml_content)

tests/test_config.py:307: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:429: in from_toml
    return cls(
stampbot/config.py:166: in __init__
    self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

patterns = ['[invalid']

    @staticmethod
    def _compile_title_patterns(patterns: list[str]) -> list[Any]:
        """Validate and compile bounded pull request title patterns.
    
        Args:
            patterns: Configured title patterns.
    
        Returns:
            Patterns compiled in Python ``re`` compatibility mode.
    
        Raises:
            ValueError: If the pattern list, an item, or a pattern is invalid.
        """
        if not isinstance(patterns, list):
            raise ValueError("required_title_patterns must be a list of strings")
        if len(patterns) > MAX_TITLE_PATTERN_COUNT:
            raise ValueError(
                "required_title_patterns contains too many patterns "
                f"(maximum: {MAX_TITLE_PATTERN_COUNT})"
            )
    
        compiled_patterns = []
        for pattern in patterns:
            if not isinstance(pattern, str):
                raise ValueError("required_title_patterns must contain only strings")
            if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
                raise ValueError(
                    "required_title_patterns contains a pattern longer than "
                    f"{MAX_TITLE_PATTERN_LENGTH} characters"
                )
            try:
                compiled_patterns.append(regex.compile(pattern, regex.VERSION0))
>           except (regex.error, CosmicRayTestingException) as e:
                                 ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/config.py:245: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_invalid_regex_pattern - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 37 passed, 3 deselected, 1 warning in 1.34s
operator: core/ExceptionReplacer, occurrence: 4, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -323,7 +323,7 @@
                         concurrent=True,
                     ):
                         break
-                except TimeoutError:
+                except CosmicRayTestingException:
                     timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
                     return (
                         False,
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

self = <stampbot.config.RepoConfig object at 0x7c800b218ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
>                   if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
E                   TimeoutError: regex timed out

stampbot/config.py:320: TimeoutError

During handling of the above exception, another exception occurred:

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
>       is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )

tests/test_config.py:445: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.config.RepoConfig object at 0x7c800b218ad0>, pr_labels = []
pr_title = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
                    if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
                        break
>               except CosmicRayTestingException:
                       ^^^^^^^^^^^^^^^^^^^^^^^^^
E               NameError: name 'CosmicRayTestingException' is not defined

stampbot/config.py:326: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.33s
operator: core/ExceptionReplacer, occurrence: 5, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -330,7 +330,7 @@
                         "PR title pattern evaluation could not complete within "
                         f"the {timeout_ms} ms safety limit",
                     )
-                except regex.error:
+                except regexCosmicRayTestingExceptionerror:
                     return False, "PR title pattern evaluation failed safely"
             else:
                 return False, "PR title does not match any required pattern"
......................................................F
=================================== FAILURES ===================================
______________ test_is_pr_eligible_regex_engine_failure_is_closed ______________

self = <stampbot.config.RepoConfig object at 0x73d912164a10>, pr_labels = []
pr_title = 'fix: dependency', pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
>                   if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):

stampbot/config.py:320: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/unittest/mock.py:1176: in __call__
    return self._mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/usr/lib/python3.14/unittest/mock.py:1180: in _mock_call
    return self._execute_mock_call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <MagicMock name='mock.search' id='127376148442016'>
args = ('fix: dependency',), kwargs = {'timeout': 0.01, 'concurrent': True}

    def _execute_mock_call(self, /, *args, **kwargs):
        # separate from _increment_mock_call so that awaited functions are
        # executed separately from their call, also AsyncMock overrides this method
    
        effect = self.side_effect
        if effect is not None:
            if _is_exception(effect):
>               raise effect
E               regex._regex_core.error: engine failure

/usr/lib/python3.14/unittest/mock.py:1241: error

During handling of the above exception, another exception occurred:

    def test_is_pr_eligible_regex_engine_failure_is_closed():
        """Test a regex engine failure cannot approve a pull request."""
        config = RepoConfig.from_toml('required_title_patterns = ["^fix:"]')
        config._compiled_title_patterns[0] = MagicMock()
        config._compiled_title_patterns[0].search.side_effect = regex.error("engine failure")
    
>       is_eligible, reason = config.is_pr_eligible([], "fix: dependency", "someuser")
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:459: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.config.RepoConfig object at 0x73d912164a10>, pr_labels = []
pr_title = 'fix: dependency', pr_author = 'someuser', author_team_slugs = None

    def is_pr_eligible(
        self,
        pr_labels: list[str],
        pr_title: str,
        pr_author: str,
        author_team_slugs: list[str] | None = None,
    ) -> tuple[bool, str | None]:
        """Check if a PR is eligible for auto-approval based on filters.
    
        All configured filters must pass (AND logic between filter types).
        Within each filter, any match is sufficient (OR logic).
    
        Args:
            pr_labels: List of label names on the PR
            pr_title: PR title
            pr_author: PR author's login
            author_team_slugs: List of team slugs the author belongs to (for team filter)
    
        Returns:
            Tuple of (is_eligible, reason_if_not_eligible)
        """
        # Check required labels filter
        if self.required_labels:
            if not any(label in self.required_labels for label in pr_labels):
                return (
                    False,
                    f"PR missing required label (one of: {', '.join(self.required_labels)})",
                )
    
        # Check required title patterns filter
        if self._compiled_title_patterns:
            if not isinstance(pr_title, str):
                return False, "PR title is not valid text"
            if len(pr_title) > MAX_PR_TITLE_LENGTH:
                return (
                    False,
                    f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                )
    
            for pattern in self._compiled_title_patterns:
                try:
                    if pattern.search(
                        pr_title,
                        timeout=TITLE_PATTERN_TIMEOUT_SECONDS,
                        concurrent=True,
                    ):
                        break
                except TimeoutError:
                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
                    return (
                        False,
                        "PR title pattern evaluation could not complete within "
                        f"the {timeout_ms} ms safety limit",
                    )
>               except regexCosmicRayTestingExceptionerror:
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E               NameError: name 'regexCosmicRayTestingExceptionerror' is not defined

stampbot/config.py:333: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_regex_engine_failure_is_closed - NameError: name 'regexCosmicRayTestingExceptionerror' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 54 passed, 3 deselected, 1 warning in 1.39s
operator: core/ExceptionReplacer, occurrence: 6, definition_name: from_toml
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -418,7 +418,7 @@
         if toml_content:
             try:
                 repo_settings = toml.loads(toml_content)
-            except Exception as e:
+            except CosmicRayTestingException as e:
                 raise ValueError(f"Invalid TOML content: {e}") from e
 
         # Merge: repo settings override defaults
.........................F
=================================== FAILURES ===================================
___________________ test_repo_config_from_toml_parser_error ____________________

cls = <class 'stampbot.config.RepoConfig'>, toml_content = ';\r'

    @classmethod
    def from_toml(cls, toml_content: str) -> RepoConfig:
        """Parse TOML content and merge with app defaults.
    
        Uses dynaconf to properly merge repo-specific settings with
        the app-level defaults, allowing repos to override only
        the settings they need to change.
    
        Args:
            toml_content: Raw TOML content from repo's stampbot.toml
    
        Returns:
            RepoConfig with merged settings
        """
        import toml
    
        # Start with defaults
        defaults = cls._get_defaults()
    
        # Parse the repo-specific TOML
        repo_settings: dict[str, Any] = {}
        if toml_content:
            try:
>               repo_settings = toml.loads(toml_content)
                                ^^^^^^^^^^^^^^^^^^^^^^^^

stampbot/config.py:420: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

s = ';\r', _dict = <class 'dict'>
decoder = <toml.decoder.TomlDecoder object at 0x780b5a5d11d0>

    def loads(s, _dict=dict, decoder=None):
        """Parses string as toml
    
        Args:
            s: String to be parsed
            _dict: (optional) Specifies the class of the returned toml dictionary
    
        Returns:
            Parsed toml file represented as a dictionary
    
        Raises:
            TypeError: When a non-string is passed
            TomlDecodeError: Error while decoding toml
        """
    
        implicitgroups = []
        if decoder is None:
            decoder = TomlDecoder(_dict)
        retval = decoder.get_empty_table()
        currentlevel = retval
        if not isinstance(s, basestring):
            raise TypeError("Expecting something like a string")
    
        if not isinstance(s, unicode):
            s = s.decode('utf8')
    
        original = s
        sl = list(s)
        openarr = 0
        openstring = False
        openstrchar = ""
        multilinestr = False
        arrayoftables = False
        beginline = True
        keygroup = False
        dottedkey = False
        keyname = 0
        key = ''
        prev_key = ''
        line_no = 1
    
        for i, item in enumerate(sl):
>           if item == '\r' and sl[i + 1] == '\n':
                                ^^^^^^^^^
E           IndexError: list index out of range

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/toml/decoder.py:207: IndexError

During handling of the above exception, another exception occurred:

    def test_repo_config_from_toml_parser_error():
        """Test parser errors are normalized as config validation errors."""
        with pytest.raises(ValueError, match="Invalid TOML content"):
>           RepoConfig.from_toml(";\r")

tests/test_config.py:176: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'stampbot.config.RepoConfig'>, toml_content = ';\r'

    @classmethod
    def from_toml(cls, toml_content: str) -> RepoConfig:
        """Parse TOML content and merge with app defaults.
    
        Uses dynaconf to properly merge repo-specific settings with
        the app-level defaults, allowing repos to override only
        the settings they need to change.
    
        Args:
            toml_content: Raw TOML content from repo's stampbot.toml
    
        Returns:
            RepoConfig with merged settings
        """
        import toml
    
        # Start with defaults
        defaults = cls._get_defaults()
    
        # Parse the repo-specific TOML
        repo_settings: dict[str, Any] = {}
        if toml_content:
            try:
                repo_settings = toml.loads(toml_content)
>           except CosmicRayTestingException as e:
                   ^^^^^^^^^^^^^^^^^^^^^^^^^
E           NameError: name 'CosmicRayTestingException' is not defined

stampbot/config.py:421: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml_parser_error - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 25 passed, 3 deselected, 1 warning in 1.36s
operator: core/ExceptionReplacer, occurrence: 7, definition_name: default_or_config_error
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -474,6 +474,6 @@
         """
         try:
             return cls.default()
-        except ValueError as e:
+        except CosmicRayTestingException as e:
             return cls.fail_closed(f"Invalid service default configuration: {e}")
 
................F
=================================== FAILURES ===================================
_ test_repo_config_default_or_config_error_fails_closed[approve_commands-approve-must be a list of strings] _

cls = <class 'stampbot.config.RepoConfig'>

    @classmethod
    def default_or_config_error(cls) -> RepoConfig:
        """Return configured defaults, failing closed if they are invalid.
    
        Returns:
            Configured defaults, or a built-in config carrying config_error.
        """
        try:
>           return cls.default()
                   ^^^^^^^^^^^^^

stampbot/config.py:476: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:454: in default
    return cls(
stampbot/config.py:156: in __init__
    self.approve_commands = self._validate_command_list(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

commands = 'approve', setting = 'approve_commands'

    @staticmethod
    def _validate_command_list(commands: object, setting: str) -> list[str]:
        """Require one command setting to be a list containing only strings.
    
        Args:
            commands: Parsed application or repository setting.
            setting: Setting name used in the validation error.
    
        Returns:
            A detached copy of the validated command list.
    
        Raises:
            ValueError: If the setting is not a list of strings.
        """
        if not isinstance(commands, list):
>           raise ValueError(f"{setting} must be a list of strings")
E           ValueError: approve_commands must be a list of strings

stampbot/config.py:187: ValueError

During handling of the above exception, another exception occurred:

setting = 'approve_commands', value = 'approve'
message = 'must be a list of strings'

    @pytest.mark.parametrize(
        ("setting", "value", "message"),
        [
            ("approve_commands", "approve", "must be a list of strings"),
            ("unapprove_commands", ["unapprove", 1], "must contain only strings"),
            (
                "chatops_required_permission",
                "owner",
                "Invalid chatops_required_permission",
            ),
            (
                "chatops_required_permission",
                ["maintain"],
                "Invalid chatops_required_permission",
            ),
        ],
    )
    def test_repo_config_default_or_config_error_fails_closed(setting, value, message):
        """Test invalid service defaults produce a non-authorizing config."""
        service_defaults = MagicMock(spec=[setting])
        setattr(service_defaults, setting, value)
    
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = service_defaults
            mock_settings.defaults = service_defaults
    
>           config = RepoConfig.default_or_config_error()
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:107: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'stampbot.config.RepoConfig'>

    @classmethod
    def default_or_config_error(cls) -> RepoConfig:
        """Return configured defaults, failing closed if they are invalid.
    
        Returns:
            Configured defaults, or a built-in config carrying config_error.
        """
        try:
            return cls.default()
>       except CosmicRayTestingException as e:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/config.py:477: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_default_or_config_error_fails_closed[approve_commands-approve-must be a list of strings] - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 16 passed, 3 deselected, 1 warning in 1.31s
operator: core/NumberReplacer, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -13,7 +13,7 @@
 # GitHub currently limits pull request titles to 256 characters. Keep a local
 # bound because webhook JSON is still untrusted input. Repository patterns are
 # bounded separately so a policy cannot multiply matching work without limit.
-MAX_PR_TITLE_LENGTH = 256
+MAX_PR_TITLE_LENGTH = 257
 MAX_TITLE_PATTERN_COUNT = 20
 MAX_TITLE_PATTERN_LENGTH = 256
 TITLE_PATTERN_TIMEOUT_SECONDS = 0.01
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/NumberReplacer, occurrence: 1, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -13,7 +13,7 @@
 # GitHub currently limits pull request titles to 256 characters. Keep a local
 # bound because webhook JSON is still untrusted input. Repository patterns are
 # bounded separately so a policy cannot multiply matching work without limit.
-MAX_PR_TITLE_LENGTH = 256
+MAX_PR_TITLE_LENGTH = 255
 MAX_TITLE_PATTERN_COUNT = 20
 MAX_TITLE_PATTERN_LENGTH = 256
 TITLE_PATTERN_TIMEOUT_SECONDS = 0.01
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/NumberReplacer, occurrence: 2, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -14,7 +14,7 @@
 # bound because webhook JSON is still untrusted input. Repository patterns are
 # bounded separately so a policy cannot multiply matching work without limit.
 MAX_PR_TITLE_LENGTH = 256
-MAX_TITLE_PATTERN_COUNT = 20
+MAX_TITLE_PATTERN_COUNT = 21
 MAX_TITLE_PATTERN_LENGTH = 256
 TITLE_PATTERN_TIMEOUT_SECONDS = 0.01
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/NumberReplacer, occurrence: 3, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -14,7 +14,7 @@
 # bound because webhook JSON is still untrusted input. Repository patterns are
 # bounded separately so a policy cannot multiply matching work without limit.
 MAX_PR_TITLE_LENGTH = 256
-MAX_TITLE_PATTERN_COUNT = 20
+MAX_TITLE_PATTERN_COUNT = 19
 MAX_TITLE_PATTERN_LENGTH = 256
 TITLE_PATTERN_TIMEOUT_SECONDS = 0.01
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.42s
operator: core/NumberReplacer, occurrence: 4, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -15,7 +15,7 @@
 # bounded separately so a policy cannot multiply matching work without limit.
 MAX_PR_TITLE_LENGTH = 256
 MAX_TITLE_PATTERN_COUNT = 20
-MAX_TITLE_PATTERN_LENGTH = 256
+MAX_TITLE_PATTERN_LENGTH = 257
 TITLE_PATTERN_TIMEOUT_SECONDS = 0.01
 
 # Default values for repo configuration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.43s
operator: core/NumberReplacer, occurrence: 5, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -15,7 +15,7 @@
 # bounded separately so a policy cannot multiply matching work without limit.
 MAX_PR_TITLE_LENGTH = 256
 MAX_TITLE_PATTERN_COUNT = 20
-MAX_TITLE_PATTERN_LENGTH = 256
+MAX_TITLE_PATTERN_LENGTH = 255
 TITLE_PATTERN_TIMEOUT_SECONDS = 0.01
 
 # Default values for repo configuration
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/NumberReplacer, occurrence: 6, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -16,7 +16,7 @@
 MAX_PR_TITLE_LENGTH = 256
 MAX_TITLE_PATTERN_COUNT = 20
 MAX_TITLE_PATTERN_LENGTH = 256
-TITLE_PATTERN_TIMEOUT_SECONDS = 0.01
+TITLE_PATTERN_TIMEOUT_SECONDS = 1.01
 
 # Default values for repo configuration
 # These can be overridden in settings.toml or per-repo stampbot.toml
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
..............................................................F
=================================== FAILURES ===================================
________________ test_pathological_pr_title_pattern_is_bounded _________________

fut = <coroutine object WebhookHandler.handle_event at 0x77d05455b100>
timeout = 1.0

    async def wait_for(fut, timeout):
        """Wait for the single Future or coroutine to complete, with timeout.
    
        Coroutine will be wrapped in Task.
    
        Returns result of the Future or coroutine.  When a timeout occurs,
        it cancels the task and raises TimeoutError.  To avoid the task
        cancellation, wrap it in shield().
    
        If the wait is cancelled, the task is also cancelled.
    
        If the task suppresses the cancellation and returns a value instead,
        that value is returned.
    
        This function is a coroutine.
        """
        # The special case for timeout <= 0 is for the following case:
        #
        # async def test_waitfor():
        #     func_started = False
        #
        #     async def func():
        #         nonlocal func_started
        #         func_started = True
        #
        #     try:
        #         await asyncio.wait_for(func(), 0)
        #     except asyncio.TimeoutError:
        #         assert not func_started
        #     else:
        #         assert False
        #
        # asyncio.run(test_waitfor())
    
    
        if timeout is not None and timeout <= 0:
            fut = ensure_future(fut)
    
            if fut.done():
                return fut.result()
    
            await _cancel_and_wait(fut)
            try:
                return fut.result()
            except exceptions.CancelledError as exc:
                raise TimeoutError from exc
    
        async with timeouts.timeout(timeout):
>           return await fut
                   ^^^^^^^^^

/usr/lib/python3.14/asyncio/tasks.py:488: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/webhook_handler.py:164: in handle_event
    result = await self._handle_pull_request(payload)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:279: in _handle_pull_request
    result = await self._approve_for_label(span, event, label)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
stampbot/webhook_handler.py:416: in _approve_for_label
    is_eligible, reason = await run_in_threadpool(
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/starlette/concurrency.py:34: in run_in_threadpool
    return await anyio.to_thread.run_sync(func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/to_thread.py:65: in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'anyio._backends._asyncio.AsyncIOBackend'>
func = functools.partial(<bound method RepoConfig.is_pr_eligible of <stampbot.config.RepoConfig object at 0x77d053049cd0>>, [...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!', 'contributor', None)
args = (), abandon_on_cancel = False, limiter = None

    @classmethod
    async def run_sync_in_worker_thread(  # type: ignore[return]
        cls,
        func: Callable[[Unpack[PosArgsT]], T_Retval],
        args: tuple[Unpack[PosArgsT]],
        abandon_on_cancel: bool = False,
        limiter: BaseCapacityLimiter | None = None,
    ) -> T_Retval:
        await cls.checkpoint()
    
        # If this is the first run in this event loop thread, set up the necessary
        # variables
        try:
            idle_workers = _threadpool_idle_workers.get()
            workers = _threadpool_workers.get()
        except LookupError:
            idle_workers = deque()
            workers = set()
            _threadpool_idle_workers.set(idle_workers)
            _threadpool_workers.set(workers)
    
        async with limiter or cls.current_default_thread_limiter():
            with CancelScope(shield=not abandon_on_cancel) as scope:
                future = asyncio.Future[T_Retval]()
                root_task = find_root_task()
                if not idle_workers:
                    worker = WorkerThread(root_task, workers, idle_workers)
                    worker.start()
                    workers.add(worker)
                    root_task.add_done_callback(worker.stop, context=Context())
                else:
                    worker = idle_workers.pop()
    
                    # Prune any other workers that have been idle for MAX_IDLE_TIME
                    # seconds or longer
                    now = cls.current_time()
                    while idle_workers:
                        if (
                            now - idle_workers[0].idle_since
                            < WorkerThread.MAX_IDLE_TIME
                        ):
                            break
    
                        expired_worker = idle_workers.popleft()
                        expired_worker.root_task.remove_done_callback(
                            expired_worker.stop
                        )
                        expired_worker.stop()
    
                context = copy_context()
                context.run(set_current_async_library, None)
                if abandon_on_cancel or scope._parent_scope is None:
                    worker_scope = scope
                else:
                    worker_scope = scope._parent_scope
    
                worker.queue.put_nowait((context, func, args, future, worker_scope))
>               return await future
                       ^^^^^^^^^^^^
E               asyncio.exceptions.CancelledError

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/anyio/_backends/_asyncio.py:2779: CancelledError

The above exception was the direct cause of the following exception:

webhook_handler = <stampbot.webhook_handler.WebhookHandler object at 0x77d053067f50>
mock_github_client = <MagicMock name='github_client' id='131736652217184'>

    @pytest.mark.asyncio
    async def test_pathological_pr_title_pattern_is_bounded(webhook_handler, mock_github_client):
        """Test a pathological title pattern fails closed within a bounded time."""
        from stampbot.config import MAX_PR_TITLE_LENGTH
    
        payload = load_fixture("pr_labeled_autoapprove")
        payload["pull_request"]["title"] = "a" * (MAX_PR_TITLE_LENGTH - 1) + "!"
        mock_github_client.get_repo_file.return_value = 'required_title_patterns = ["(a|aa)+$"]'
    
>       result = await asyncio.wait_for(
            webhook_handler.handle_event("pull_request", payload), timeout=1.0
        )

tests/test_webhook_handler.py:766: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
/usr/lib/python3.14/asyncio/tasks.py:487: in wait_for
    async with timeouts.timeout(timeout):
               ^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Timeout [expired]>
exc_type = <class 'asyncio.exceptions.CancelledError'>
exc_val = CancelledError(), exc_tb = <traceback object at 0x77d0530b0880>

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None:
        assert self._state in (_State.ENTERED, _State.EXPIRING)
    
        if self._timeout_handler is not None:
            self._timeout_handler.cancel()
            self._timeout_handler = None
    
        if self._state is _State.EXPIRING:
            self._state = _State.EXPIRED
    
            if self._task.uncancel() <= self._cancelling and exc_type is not None:
                # Since there are no new cancel requests, we're
                # handling this.
                if issubclass(exc_type, exceptions.CancelledError):
>                   raise TimeoutError from exc_val
E                   TimeoutError

/usr/lib/python3.14/asyncio/timeouts.py:114: TimeoutError
----------------------------- Captured stdout call -----------------------------
{"extra": {"event_type": "pull_request", "action": "labeled"}, "event": "Received pull_request event", "logger": "stampbot.webhook_handler", "level": "info", "timestamp": "2026-11-04T03:36:53.632466Z", "_logger": "<_FixedFindCallerLogger stampbot.webhook_handler (INFO)>", "_name": "info"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.webhook_handler:webhook_handler.py:156 {'extra': {'event_type': 'pull_request', 'action': 'labeled'}, 'event': 'Received pull_request event', 'logger': 'stampbot.webhook_handler', 'level': 'info', 'timestamp': '2026-11-04T03:36:53.632466Z'}
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_webhook_handler.py::test_pathological_pr_title_pattern_is_bounded - TimeoutError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 422 passed, 3 deselected, 1 warning in 4.59s
operator: core/NumberReplacer, occurrence: 7, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -16,7 +16,7 @@
 MAX_PR_TITLE_LENGTH = 256
 MAX_TITLE_PATTERN_COUNT = 20
 MAX_TITLE_PATTERN_LENGTH = 256
-TITLE_PATTERN_TIMEOUT_SECONDS = 0.01
+TITLE_PATTERN_TIMEOUT_SECONDS = -0.99
 
 # Default values for repo configuration
 # These can be overridden in settings.toml or per-repo stampbot.toml
timeout
operator: core/NumberReplacer, occurrence: 8, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -68,7 +68,7 @@
     )
 
 
-DEFAULT_REPO_CONFIG_CACHE_SECONDS = 300
+DEFAULT_REPO_CONFIG_CACHE_SECONDS = 301
 
 
 def repo_config_cache_seconds() -> int:
........................................................................ [ 15%]
.F
=================================== FAILURES ===================================
___________________ test_repo_config_cache_seconds_defaults ____________________

    def test_repo_config_cache_seconds_defaults():
        """Test the cache lifetime defaults to five minutes."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.side_effect = lambda _key, default=None: default
    
>           assert repo_config_cache_seconds() == 300
E           assert 301 == 300
E            +  where 301 = repo_config_cache_seconds()

tests/test_config.py:635: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_cache_seconds_defaults - assert 301 == 300
 +  where 301 = repo_config_cache_seconds()
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 73 passed, 3 deselected, 1 warning in 1.31s
operator: core/NumberReplacer, occurrence: 9, definition_name: None
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -68,7 +68,7 @@
     )
 
 
-DEFAULT_REPO_CONFIG_CACHE_SECONDS = 300
+DEFAULT_REPO_CONFIG_CACHE_SECONDS = 299
 
 
 def repo_config_cache_seconds() -> int:
........................................................................ [ 15%]
.F
=================================== FAILURES ===================================
___________________ test_repo_config_cache_seconds_defaults ____________________

    def test_repo_config_cache_seconds_defaults():
        """Test the cache lifetime defaults to five minutes."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.side_effect = lambda _key, default=None: default
    
>           assert repo_config_cache_seconds() == 300
E           assert 299 == 300
E            +  where 299 = repo_config_cache_seconds()

tests/test_config.py:635: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_cache_seconds_defaults - assert 299 == 300
 +  where 299 = repo_config_cache_seconds()
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 73 passed, 3 deselected, 1 warning in 1.36s
operator: core/NumberReplacer, occurrence: 10, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -90,7 +90,7 @@
         seconds = int(raw)
     except (TypeError, ValueError) as error:
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
-    if seconds < 0:
+    if seconds < 1:
         raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
     return seconds
 
.......................................................................F [ 15%]

=================================== FAILURES ===================================
_____________ test_repo_config_cache_seconds_accepts_integers[0-0] _____________

raw = 0, expected = 0

    @pytest.mark.parametrize(
        ("raw", "expected"),
        [(300, 300), ("120", 120), (0, 0), (5.0, 5)],
    )
    def test_repo_config_cache_seconds_accepts_integers(raw, expected):
        """Test the cache lifetime accepts integer-valued settings."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = raw
    
>           assert repo_config_cache_seconds() == expected
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:627: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def repo_config_cache_seconds() -> int:
        """Return how long a repository's parsed policy stays cached, in seconds.
    
        Returns:
            A non-negative integer. Zero disables the cache.
    
        Raises:
            ValueError: If ``repo_config_cache_seconds`` is not a non-negative integer.
        """
        raw = settings.get("repo_config_cache_seconds", DEFAULT_REPO_CONFIG_CACHE_SECONDS)
        # Dynaconf parses environment values, so ``false`` arrives as a bool and
        # ``1.9`` as a float. Neither is an integer setting, and int() would accept
        # both silently.
        if isinstance(raw, bool) or (isinstance(raw, float) and not raw.is_integer()):
            raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}")
        try:
            seconds = int(raw)
        except (TypeError, ValueError) as error:
            raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
        if seconds < 1:
>           raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
E           ValueError: repo_config_cache_seconds must be 0 or greater, got 0

stampbot/config.py:94: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_cache_seconds_accepts_integers[0-0] - ValueError: repo_config_cache_seconds must be 0 or greater, got 0
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 71 passed, 3 deselected, 1 warning in 1.33s
operator: core/NumberReplacer, occurrence: 11, definition_name: repo_config_cache_seconds
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -90,7 +90,7 @@
         seconds = int(raw)
     except (TypeError, ValueError) as error:
         raise ValueError(f"repo_config_cache_seconds must be an integer, got {raw!r}") from error
-    if seconds < 0:
+    if seconds < -1:
         raise ValueError(f"repo_config_cache_seconds must be 0 or greater, got {raw!r}")
     return seconds
 
........................................................................ [ 15%]
.....F
=================================== FAILURES ===================================
__________ test_repo_config_cache_seconds_rejects_invalid_values[-1] ___________

raw = '-1'

    @pytest.mark.parametrize("raw", ["soon", None, -5, "-1", [300], True, False, 1.9, "1.9"])
    def test_repo_config_cache_seconds_rejects_invalid_values(raw):
        """Test a non-integer or negative lifetime fails at startup instead of guessing."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = raw
    
>           with pytest.raises(ValueError, match="repo_config_cache_seconds"):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           Failed: DID NOT RAISE ValueError

tests/test_config.py:644: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_cache_seconds_rejects_invalid_values[-1] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 77 passed, 3 deselected, 1 warning in 1.32s
operator: core/NumberReplacer, occurrence: 12, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1001)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 13, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -324,7 +324,7 @@
                     ):
                         break
                 except TimeoutError:
-                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)
+                    timeout_ms = int(TITLE_PATTERN_TIMEOUT_SECONDS * 999)
                     return (
                         False,
                         "PR title pattern evaluation could not complete within "
.....................................................F
=================================== FAILURES ===================================
___________ test_is_pr_eligible_pathological_title_pattern_times_out ___________

    def test_is_pr_eligible_pathological_title_pattern_times_out():
        """Test catastrophic backtracking is stopped by the match timeout."""
        config = RepoConfig.from_toml('required_title_patterns = ["(a|aa)+$"]')
    
        is_eligible, reason = config.is_pr_eligible(
            [], "a" * (MAX_PR_TITLE_LENGTH - 1) + "!", "someuser"
        )
    
        assert is_eligible is False
>       assert f"{int(TITLE_PATTERN_TIMEOUT_SECONDS * 1000)} ms safety limit" in reason
E       AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 9 ms safety limit'

tests/test_config.py:450: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_pathological_title_pattern_times_out - AssertionError: assert '10 ms safety limit' in 'PR title pattern evaluation could not complete within the 9 ms safety limit'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 53 passed, 3 deselected, 1 warning in 1.34s
operator: core/NumberReplacer, occurrence: 14, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -345,7 +345,7 @@
             if self.allowed_teams and author_team_slugs:
                 for team in self.allowed_teams:
                     # Support both "org/team" and "team" formats
-                    team_slug = team.split("/")[-1] if "/" in team else team
+                    team_slug = team.split("/")[- 2] if "/" in team else team
                     if team_slug in author_team_slugs:
                         return True, None
 
............................................................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________

    def test_is_pr_eligible_allowed_team():
        """Test is_pr_eligible returns True when author is in an allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "team-member", author_team_slugs=["release-team"]
        )
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:523: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected, 1 warning in 1.33s
operator: core/NumberReplacer, occurrence: 15, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -345,7 +345,7 @@
             if self.allowed_teams and author_team_slugs:
                 for team in self.allowed_teams:
                     # Support both "org/team" and "team" formats
-                    team_slug = team.split("/")[-1] if "/" in team else team
+                    team_slug = team.split("/")[- 0] if "/" in team else team
                     if team_slug in author_team_slugs:
                         return True, None
 
............................................................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________

    def test_is_pr_eligible_allowed_team():
        """Test is_pr_eligible returns True when author is in an allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "team-member", author_team_slugs=["release-team"]
        )
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:523: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected, 1 warning in 1.34s
operator: core/RemoveDecorator, occurrence: 0, definition_name: RepoConfig
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -168,8 +168,6 @@
         self.allowed_users = allowed_users or []
         self.allowed_teams = allowed_teams or []
         self.config_error = config_error
-
-    @staticmethod
     def _validate_command_list(commands: object, setting: str) -> list[str]:
         """Require one command setting to be a list containing only strings.
 
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:427: in from_toml
    return cls(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.config.RepoConfig object at 0x7c2864a07a10>
approval_labels = ['test', 'autoapprove'], auto_approve_on_label = True
reapprove = True, chatops_enabled = True, chatops_required_permission = 'write'
approve_commands = ['approve', 'stamp'], unapprove_commands = ['unapprove']
required_labels = [], required_title_patterns = [], allowed_users = []
allowed_teams = [], config_error = None

    def __init__(
        self,
        approval_labels: list[str],
        auto_approve_on_label: bool,
        reapprove: bool,
        chatops_enabled: bool,
        chatops_required_permission: str,
        approve_commands: list[str],
        unapprove_commands: list[str],
        required_labels: list[str] | None = None,
        required_title_patterns: list[str] | None = None,
        allowed_users: list[str] | None = None,
        allowed_teams: list[str] | None = None,
        config_error: str | None = None,
    ):
        """Initialize repo configuration.
    
        Args:
            approval_labels: Labels that trigger auto-approval
            auto_approve_on_label: Whether to auto-approve when label is added
            reapprove: Whether to re-approve when new commits are pushed
            chatops_enabled: Whether chatops commands are enabled
            chatops_required_permission: Minimum repo permission for chatops commands
            approve_commands: Commands that trigger approval
            unapprove_commands: Commands that dismiss approval
            required_labels: Labels required for auto-approval eligibility (any match)
            required_title_patterns: Regex patterns for PR title (any match)
            allowed_users: User logins allowed for auto-approval (any match)
            allowed_teams: Team slugs allowed for auto-approval (any match)
            config_error: Config error message if config was invalid
        """
        self.approval_labels = approval_labels
        self.auto_approve_on_label = auto_approve_on_label
        self.reapprove = reapprove
        self.chatops_enabled = chatops_enabled
        self.chatops_required_permission = self._validate_required_permission(
            chatops_required_permission
        )
>       self.approve_commands = self._validate_command_list(
            approve_commands,
            "approve_commands",
        )
E       TypeError: RepoConfig._validate_command_list() takes 2 positional arguments but 3 were given

stampbot/config.py:156: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - TypeError: RepoConfig._validate_command_list() takes 2 positional arguments but 3 were given
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.30s
operator: core/RemoveDecorator, occurrence: 1, definition_name: RepoConfig
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -188,8 +188,6 @@
         if any(not isinstance(command, str) for command in commands):
             raise ValueError(f"{setting} must contain only strings")
         return list(commands)
-
-    @staticmethod
     def _validate_required_permission(permission: object) -> str:
         """Require the ChatOps permission threshold to be a supported value.
 
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:427: in from_toml
    return cls(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.config.RepoConfig object at 0x7cb5b920fa10>
approval_labels = ['test', 'autoapprove'], auto_approve_on_label = True
reapprove = True, chatops_enabled = True, chatops_required_permission = 'write'
approve_commands = ['approve', 'stamp'], unapprove_commands = ['unapprove']
required_labels = [], required_title_patterns = [], allowed_users = []
allowed_teams = [], config_error = None

    def __init__(
        self,
        approval_labels: list[str],
        auto_approve_on_label: bool,
        reapprove: bool,
        chatops_enabled: bool,
        chatops_required_permission: str,
        approve_commands: list[str],
        unapprove_commands: list[str],
        required_labels: list[str] | None = None,
        required_title_patterns: list[str] | None = None,
        allowed_users: list[str] | None = None,
        allowed_teams: list[str] | None = None,
        config_error: str | None = None,
    ):
        """Initialize repo configuration.
    
        Args:
            approval_labels: Labels that trigger auto-approval
            auto_approve_on_label: Whether to auto-approve when label is added
            reapprove: Whether to re-approve when new commits are pushed
            chatops_enabled: Whether chatops commands are enabled
            chatops_required_permission: Minimum repo permission for chatops commands
            approve_commands: Commands that trigger approval
            unapprove_commands: Commands that dismiss approval
            required_labels: Labels required for auto-approval eligibility (any match)
            required_title_patterns: Regex patterns for PR title (any match)
            allowed_users: User logins allowed for auto-approval (any match)
            allowed_teams: Team slugs allowed for auto-approval (any match)
            config_error: Config error message if config was invalid
        """
        self.approval_labels = approval_labels
        self.auto_approve_on_label = auto_approve_on_label
        self.reapprove = reapprove
        self.chatops_enabled = chatops_enabled
>       self.chatops_required_permission = self._validate_required_permission(
            chatops_required_permission
        )
E       TypeError: RepoConfig._validate_required_permission() takes 1 positional argument but 2 were given

stampbot/config.py:153: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - TypeError: RepoConfig._validate_required_permission() takes 1 positional argument but 2 were given
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.30s
operator: core/RemoveDecorator, occurrence: 2, definition_name: RepoConfig
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -209,8 +209,6 @@
                 f"Valid values: {', '.join(REPO_PERMISSION_LEVELS)}"
             )
         return permission
-
-    @staticmethod
     def _compile_title_patterns(patterns: list[str]) -> list[Any]:
         """Validate and compile bounded pull request title patterns.
 
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/config.py:427: in from_toml
    return cls(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <stampbot.config.RepoConfig object at 0x71b17e81fa10>
approval_labels = ['test', 'autoapprove'], auto_approve_on_label = True
reapprove = True, chatops_enabled = True, chatops_required_permission = 'write'
approve_commands = ['approve', 'stamp'], unapprove_commands = ['unapprove']
required_labels = [], required_title_patterns = [], allowed_users = []
allowed_teams = [], config_error = None

    def __init__(
        self,
        approval_labels: list[str],
        auto_approve_on_label: bool,
        reapprove: bool,
        chatops_enabled: bool,
        chatops_required_permission: str,
        approve_commands: list[str],
        unapprove_commands: list[str],
        required_labels: list[str] | None = None,
        required_title_patterns: list[str] | None = None,
        allowed_users: list[str] | None = None,
        allowed_teams: list[str] | None = None,
        config_error: str | None = None,
    ):
        """Initialize repo configuration.
    
        Args:
            approval_labels: Labels that trigger auto-approval
            auto_approve_on_label: Whether to auto-approve when label is added
            reapprove: Whether to re-approve when new commits are pushed
            chatops_enabled: Whether chatops commands are enabled
            chatops_required_permission: Minimum repo permission for chatops commands
            approve_commands: Commands that trigger approval
            unapprove_commands: Commands that dismiss approval
            required_labels: Labels required for auto-approval eligibility (any match)
            required_title_patterns: Regex patterns for PR title (any match)
            allowed_users: User logins allowed for auto-approval (any match)
            allowed_teams: Team slugs allowed for auto-approval (any match)
            config_error: Config error message if config was invalid
        """
        self.approval_labels = approval_labels
        self.auto_approve_on_label = auto_approve_on_label
        self.reapprove = reapprove
        self.chatops_enabled = chatops_enabled
        self.chatops_required_permission = self._validate_required_permission(
            chatops_required_permission
        )
        self.approve_commands = self._validate_command_list(
            approve_commands,
            "approve_commands",
        )
        self.unapprove_commands = self._validate_command_list(
            unapprove_commands,
            "unapprove_commands",
        )
        self.required_labels = required_labels or []
        title_patterns = required_title_patterns if required_title_patterns is not None else []
>       self._compiled_title_patterns = self._compile_title_patterns(title_patterns)
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: RepoConfig._compile_title_patterns() takes 1 positional argument but 2 were given

stampbot/config.py:166: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - TypeError: RepoConfig._compile_title_patterns() takes 1 positional argument but 2 were given
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.30s
operator: core/RemoveDecorator, occurrence: 3, definition_name: RepoConfig
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -246,8 +246,6 @@
                 raise ValueError(f"Invalid regex pattern '{pattern}': {e}") from e
 
         return compiled_patterns
-
-    @classmethod
     def fail_closed(cls, message: str) -> RepoConfig:
         """Return a valid config object that cannot authorize automation.
 
.............F
=================================== FAILURES ===================================
_________ test_repo_config_fail_closed_does_not_read_service_defaults __________

    def test_repo_config_fail_closed_does_not_read_service_defaults():
        """Test fail-closed config is independent of invalid service settings."""
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.side_effect = AssertionError("service settings must not be read")
    
>           config = RepoConfig.fail_closed("invalid service configuration")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: RepoConfig.fail_closed() missing 1 required positional argument: 'message'

tests/test_config.py:59: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_fail_closed_does_not_read_service_defaults - TypeError: RepoConfig.fail_closed() missing 1 required positional argument: 'message'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 13 passed, 3 deselected, 1 warning in 1.32s
operator: core/RemoveDecorator, occurrence: 4, definition_name: RepoConfig
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -373,8 +373,6 @@
         if self.allowed_users and pr_author in self.allowed_users:
             return False
         return True
-
-    @classmethod
     def _get_defaults(cls) -> dict[str, Any]:
         """Get default repo config values.
 
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_config.py:31: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'stampbot.config.RepoConfig'>
toml_content = '\napproval_labels = ["test", "autoapprove"]\nauto_approve_on_label = true\nreapprove = true\nchatops_enabled = true\nchatops_required_permission = "write"\napprove_commands = ["approve", "stamp"]\nunapprove_commands = ["unapprove"]\n'

    @classmethod
    def from_toml(cls, toml_content: str) -> RepoConfig:
        """Parse TOML content and merge with app defaults.
    
        Uses dynaconf to properly merge repo-specific settings with
        the app-level defaults, allowing repos to override only
        the settings they need to change.
    
        Args:
            toml_content: Raw TOML content from repo's stampbot.toml
    
        Returns:
            RepoConfig with merged settings
        """
        import toml
    
        # Start with defaults
>       defaults = cls._get_defaults()
                   ^^^^^^^^^^^^^^^^^^^
E       TypeError: RepoConfig._get_defaults() missing 1 required positional argument: 'cls'

stampbot/config.py:412: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - TypeError: RepoConfig._get_defaults() missing 1 required positional argument: 'cls'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.30s
operator: core/RemoveDecorator, occurrence: 5, definition_name: RepoConfig
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -393,8 +393,6 @@
                     defaults[key] = getattr(settings_defaults, key)
 
         return defaults
-
-    @classmethod
     def from_toml(cls, toml_content: str) -> RepoConfig:
         """Parse TOML content and merge with app defaults.
 
...........F
=================================== FAILURES ===================================
__________________________ test_repo_config_from_toml __________________________

    def test_repo_config_from_toml():
        """Test parsing repository config from TOML."""
        toml_content = """
    approval_labels = ["test", "autoapprove"]
    auto_approve_on_label = true
    reapprove = true
    chatops_enabled = true
    chatops_required_permission = "write"
    approve_commands = ["approve", "stamp"]
    unapprove_commands = ["unapprove"]
    """
>       config = RepoConfig.from_toml(toml_content)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: RepoConfig.from_toml() missing 1 required positional argument: 'toml_content'

tests/test_config.py:31: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_from_toml - TypeError: RepoConfig.from_toml() missing 1 required positional argument: 'toml_content'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 11 passed, 3 deselected, 1 warning in 1.30s
operator: core/RemoveDecorator, occurrence: 6, definition_name: RepoConfig
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -442,8 +442,6 @@
             allowed_users=merged.get("allowed_users", []),
             allowed_teams=merged.get("allowed_teams", []),
         )
-
-    @classmethod
     def default(cls) -> RepoConfig:
         """Return default configuration from app settings.
 
............F
=================================== FAILURES ===================================
___________________________ test_repo_config_default ___________________________

    def test_repo_config_default():
        """Test default repository config."""
>       config = RepoConfig.default()
                 ^^^^^^^^^^^^^^^^^^^^
E       TypeError: RepoConfig.default() missing 1 required positional argument: 'cls'

tests/test_config.py:44: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_default - TypeError: RepoConfig.default() missing 1 required positional argument: 'cls'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 12 passed, 3 deselected, 1 warning in 1.31s
operator: core/RemoveDecorator, occurrence: 7, definition_name: RepoConfig
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -464,8 +464,6 @@
             allowed_users=defaults["allowed_users"],
             allowed_teams=defaults["allowed_teams"],
         )
-
-    @classmethod
     def default_or_config_error(cls) -> RepoConfig:
         """Return configured defaults, failing closed if they are invalid.
 
...............F
=================================== FAILURES ===================================
______ test_repo_config_default_or_config_error_preserves_valid_defaults _______

    def test_repo_config_default_or_config_error_preserves_valid_defaults():
        """Test valid configured defaults remain active."""
>       config = RepoConfig.default_or_config_error()
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       TypeError: RepoConfig.default_or_config_error() missing 1 required positional argument: 'cls'

tests/test_config.py:75: TypeError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_default_or_config_error_preserves_valid_defaults - TypeError: RepoConfig.default_or_config_error() missing 1 required positional argument: 'cls'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 15 passed, 3 deselected, 1 warning in 1.31s
operator: core/ZeroIterationForLoop, occurrence: 0, definition_name: _compile_title_patterns
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -232,7 +232,7 @@
             )
 
         compiled_patterns = []
-        for pattern in patterns:
+        for pattern in []:
             if not isinstance(pattern, str):
                 raise ValueError("required_title_patterns must contain only strings")
             if len(pattern) > MAX_TITLE_PATTERN_LENGTH:
.....................................F
=================================== FAILURES ===================================
____________________ test_repo_config_invalid_regex_pattern ____________________

    def test_repo_config_invalid_regex_pattern():
        """Test invalid regex pattern raises a ValueError."""
        toml_content = """
    required_title_patterns = ["[invalid"]
    """
>       with pytest.raises(ValueError, match="Invalid regex pattern"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_config.py:306: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_invalid_regex_pattern - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 37 passed, 3 deselected, 1 warning in 1.30s
operator: core/ZeroIterationForLoop, occurrence: 1, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -315,7 +315,7 @@
                     f"PR title exceeds the {MAX_PR_TITLE_LENGTH}-character safety limit",
                 )
 
-            for pattern in self._compiled_title_patterns:
+            for pattern in []:
                 try:
                     if pattern.search(
                         pr_title,
...............................................F
=================================== FAILURES ===================================
__________________ test_is_pr_eligible_title_pattern_matches ___________________

    def test_is_pr_eligible_title_pattern_matches():
        """Test is_pr_eligible returns True when title matches pattern."""
        config = RepoConfig.from_toml('required_title_patterns = ["^chore:", "^\\\\[bot\\\\]"]')
        is_eligible, reason = config.is_pr_eligible([], "chore: update dependencies", "someuser")
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:386: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_title_pattern_matches - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 47 passed, 3 deselected, 1 warning in 1.32s
operator: core/ZeroIterationForLoop, occurrence: 2, definition_name: is_pr_eligible
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -343,7 +343,7 @@
 
             # Or if they're a member of an allowed team
             if self.allowed_teams and author_team_slugs:
-                for team in self.allowed_teams:
+                for team in []:
                     # Support both "org/team" and "team" formats
                     team_slug = team.split("/")[-1] if "/" in team else team
                     if team_slug in author_team_slugs:
............................................................F
=================================== FAILURES ===================================
_______________________ test_is_pr_eligible_allowed_team _______________________

    def test_is_pr_eligible_allowed_team():
        """Test is_pr_eligible returns True when author is in an allowed team."""
        config = RepoConfig.from_toml('allowed_teams = ["my-org/release-team"]')
        is_eligible, reason = config.is_pr_eligible(
            [], "Update deps", "team-member", author_team_slugs=["release-team"]
        )
>       assert is_eligible is True
E       assert False is True

tests/test_config.py:523: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_is_pr_eligible_allowed_team - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 60 passed, 3 deselected, 1 warning in 1.32s
operator: core/ZeroIterationForLoop, occurrence: 3, definition_name: _get_defaults
--- mutation diff ---
--- a/stampbot/config.py
+++ b/stampbot/config.py
@@ -388,7 +388,7 @@
         # Allow overriding defaults via settings.toml [defaults] section
         if settings.get("defaults"):
             settings_defaults = settings.defaults
-            for key in defaults:
+            for key in []:
                 if hasattr(settings_defaults, key):
                     defaults[key] = getattr(settings_defaults, key)
 
................F
=================================== FAILURES ===================================
_ test_repo_config_default_or_config_error_fails_closed[approve_commands-approve-must be a list of strings] _

setting = 'approve_commands', value = 'approve'
message = 'must be a list of strings'

    @pytest.mark.parametrize(
        ("setting", "value", "message"),
        [
            ("approve_commands", "approve", "must be a list of strings"),
            ("unapprove_commands", ["unapprove", 1], "must contain only strings"),
            (
                "chatops_required_permission",
                "owner",
                "Invalid chatops_required_permission",
            ),
            (
                "chatops_required_permission",
                ["maintain"],
                "Invalid chatops_required_permission",
            ),
        ],
    )
    def test_repo_config_default_or_config_error_fails_closed(setting, value, message):
        """Test invalid service defaults produce a non-authorizing config."""
        service_defaults = MagicMock(spec=[setting])
        setattr(service_defaults, setting, value)
    
        with patch("stampbot.config.settings") as mock_settings:
            mock_settings.get.return_value = service_defaults
            mock_settings.defaults = service_defaults
    
            config = RepoConfig.default_or_config_error()
    
>       assert config.config_error is not None
E       assert None is not None
E        +  where None = <stampbot.config.RepoConfig object at 0x72fb9b6757f0>.config_error

tests/test_config.py:109: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_config.py::test_repo_config_default_or_config_error_fails_closed[approve_commands-approve-must be a list of strings] - assert None is not None
 +  where None = <stampbot.config.RepoConfig object at 0x72fb9b6757f0>.config_error
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 16 passed, 3 deselected, 1 warning in 1.31s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/__main__.py
+++ b/stampbot/__main__.py
@@ -7,7 +7,7 @@
 
 from stampbot.config import settings
 
-if __name__ == "__main__":
+if __name__ != "__main__":
     uvicorn.run(
         "stampbot.main:app",
         host=settings.host,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/__main__.py
+++ b/stampbot/__main__.py
@@ -7,7 +7,7 @@
 
 from stampbot.config import settings
 
-if __name__ == "__main__":
+if __name__ < "__main__":
     uvicorn.run(
         "stampbot.main:app",
         host=settings.host,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/__main__.py
+++ b/stampbot/__main__.py
@@ -7,7 +7,7 @@
 
 from stampbot.config import settings
 
-if __name__ == "__main__":
+if __name__ <= "__main__":
     uvicorn.run(
         "stampbot.main:app",
         host=settings.host,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/__main__.py
+++ b/stampbot/__main__.py
@@ -7,7 +7,7 @@
 
 from stampbot.config import settings
 
-if __name__ == "__main__":
+if __name__ > "__main__":
     uvicorn.run(
         "stampbot.main:app",
         host=settings.host,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.43s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/__main__.py
+++ b/stampbot/__main__.py
@@ -7,7 +7,7 @@
 
 from stampbot.config import settings
 
-if __name__ == "__main__":
+if __name__ >= "__main__":
     uvicorn.run(
         "stampbot.main:app",
         host=settings.host,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/__main__.py
+++ b/stampbot/__main__.py
@@ -7,7 +7,7 @@
 
 from stampbot.config import settings
 
-if __name__ == "__main__":
+if __name__ is "__main__":
     uvicorn.run(
         "stampbot.main:app",
         host=settings.host,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/__main__.py
+++ b/stampbot/__main__.py
@@ -7,7 +7,7 @@
 
 from stampbot.config import settings
 
-if __name__ == "__main__":
+if __name__ is not "__main__":
     uvicorn.run(
         "stampbot.main:app",
         host=settings.host,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/AddNot, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/__main__.py
+++ b/stampbot/__main__.py
@@ -7,7 +7,7 @@
 
 from stampbot.config import settings
 
-if __name__ == "__main__":
+if not __name__ == "__main__":
     uvicorn.run(
         "stampbot.main:app",
         host=settings.host,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceFalseWithTrue, occurrence: 0, definition_name: None
--- mutation diff ---
--- a/stampbot/__main__.py
+++ b/stampbot/__main__.py
@@ -13,6 +13,6 @@
         host=settings.host,
         port=settings.port,
         log_level=settings.log_level.lower(),
-        reload=False,
+        reload=True,
     )
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/ReplaceComparisonOperator_Eq_NotEq, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -31,7 +31,7 @@
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
-    if parsed.scheme == "http":
+    if parsed.scheme != "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
     elif parsed.scheme != "https":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________

self = <tests.test_manifest.TestUrlValidation object at 0x76d9023bd090>

    def test_allows_http_localhost(self):
        """Test that HTTP URLs for localhost are allowed."""
>       manifest = create_manifest(
            redirect_url="http://localhost:8000/setup/callback",
            webhook_url="http://localhost:8000/webhook",
        )

tests/test_manifest.py:33: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:130: in create_manifest
    _validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

url = 'http://localhost:8000/setup/callback', name = 'redirect_url'

    def _validate_url(url: str, name: str) -> None:
        """Validate that a URL is well-formed and uses HTTPS (or localhost).
    
        Args:
            url: URL to validate
            name: Name of the URL parameter for error messages
    
        Raises:
            ValueError: If URL is invalid or doesn't use HTTPS
        """
        parsed = urlparse(url)
    
        if not parsed.scheme or not parsed.netloc:
            raise ValueError(f"Invalid {name}: must be a complete URL")
    
        # Allow http only for localhost (development)
        if parsed.scheme != "http":
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
                raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
        elif parsed.scheme != "https":
>           raise ValueError(f"Invalid {name}: must use HTTPS")
E           ValueError: Invalid redirect_url: must use HTTPS

stampbot/manifest.py:38: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 248 passed, 3 deselected, 1 warning in 2.20s
operator: core/ReplaceComparisonOperator_Eq_Lt, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -31,7 +31,7 @@
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
-    if parsed.scheme == "http":
+    if parsed.scheme < "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
     elif parsed.scheme != "https":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________

self = <tests.test_manifest.TestUrlValidation object at 0x7ab7044a9090>

    def test_allows_http_localhost(self):
        """Test that HTTP URLs for localhost are allowed."""
>       manifest = create_manifest(
            redirect_url="http://localhost:8000/setup/callback",
            webhook_url="http://localhost:8000/webhook",
        )

tests/test_manifest.py:33: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:130: in create_manifest
    _validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

url = 'http://localhost:8000/setup/callback', name = 'redirect_url'

    def _validate_url(url: str, name: str) -> None:
        """Validate that a URL is well-formed and uses HTTPS (or localhost).
    
        Args:
            url: URL to validate
            name: Name of the URL parameter for error messages
    
        Raises:
            ValueError: If URL is invalid or doesn't use HTTPS
        """
        parsed = urlparse(url)
    
        if not parsed.scheme or not parsed.netloc:
            raise ValueError(f"Invalid {name}: must be a complete URL")
    
        # Allow http only for localhost (development)
        if parsed.scheme < "http":
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
                raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
        elif parsed.scheme != "https":
>           raise ValueError(f"Invalid {name}: must use HTTPS")
E           ValueError: Invalid redirect_url: must use HTTPS

stampbot/manifest.py:38: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 248 passed, 3 deselected, 1 warning in 2.24s
operator: core/ReplaceComparisonOperator_Eq_LtE, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -31,7 +31,7 @@
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
-    if parsed.scheme == "http":
+    if parsed.scheme <= "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
     elif parsed.scheme != "https":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.33s
operator: core/ReplaceComparisonOperator_Eq_Gt, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -31,7 +31,7 @@
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
-    if parsed.scheme == "http":
+    if parsed.scheme > "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
     elif parsed.scheme != "https":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________

self = <tests.test_manifest.TestUrlValidation object at 0x767b3b4c9090>

    def test_allows_http_localhost(self):
        """Test that HTTP URLs for localhost are allowed."""
>       manifest = create_manifest(
            redirect_url="http://localhost:8000/setup/callback",
            webhook_url="http://localhost:8000/webhook",
        )

tests/test_manifest.py:33: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:130: in create_manifest
    _validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

url = 'http://localhost:8000/setup/callback', name = 'redirect_url'

    def _validate_url(url: str, name: str) -> None:
        """Validate that a URL is well-formed and uses HTTPS (or localhost).
    
        Args:
            url: URL to validate
            name: Name of the URL parameter for error messages
    
        Raises:
            ValueError: If URL is invalid or doesn't use HTTPS
        """
        parsed = urlparse(url)
    
        if not parsed.scheme or not parsed.netloc:
            raise ValueError(f"Invalid {name}: must be a complete URL")
    
        # Allow http only for localhost (development)
        if parsed.scheme > "http":
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
                raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
        elif parsed.scheme != "https":
>           raise ValueError(f"Invalid {name}: must use HTTPS")
E           ValueError: Invalid redirect_url: must use HTTPS

stampbot/manifest.py:38: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 248 passed, 3 deselected, 1 warning in 2.21s
operator: core/ReplaceComparisonOperator_Eq_GtE, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -31,7 +31,7 @@
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
-    if parsed.scheme == "http":
+    if parsed.scheme >= "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
     elif parsed.scheme != "https":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x7d001376a200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:68: in validate_base_url
    _validate_url(normalized, "base_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

url = 'https://stampbot.example.test', name = 'base_url'

    def _validate_url(url: str, name: str) -> None:
        """Validate that a URL is well-formed and uses HTTPS (or localhost).
    
        Args:
            url: URL to validate
            name: Name of the URL parameter for error messages
    
        Raises:
            ValueError: If URL is invalid or doesn't use HTTPS
        """
        parsed = urlparse(url)
    
        if not parsed.scheme or not parsed.netloc:
            raise ValueError(f"Invalid {name}: must be a complete URL")
    
        # Allow http only for localhost (development)
        if parsed.scheme >= "http":
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
>               raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
E               ValueError: Invalid base_url: must use HTTPS (HTTP only allowed for localhost)

stampbot/manifest.py:36: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: must use HTTPS (HTTP only allowed for localhost)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceComparisonOperator_Eq_Is, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -31,7 +31,7 @@
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
-    if parsed.scheme == "http":
+    if parsed.scheme is "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
     elif parsed.scheme != "https":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________

self = <tests.test_manifest.TestUrlValidation object at 0x756a68ab1090>

    def test_allows_http_localhost(self):
        """Test that HTTP URLs for localhost are allowed."""
>       manifest = create_manifest(
            redirect_url="http://localhost:8000/setup/callback",
            webhook_url="http://localhost:8000/webhook",
        )

tests/test_manifest.py:33: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:130: in create_manifest
    _validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

url = 'http://localhost:8000/setup/callback', name = 'redirect_url'

    def _validate_url(url: str, name: str) -> None:
        """Validate that a URL is well-formed and uses HTTPS (or localhost).
    
        Args:
            url: URL to validate
            name: Name of the URL parameter for error messages
    
        Raises:
            ValueError: If URL is invalid or doesn't use HTTPS
        """
        parsed = urlparse(url)
    
        if not parsed.scheme or not parsed.netloc:
            raise ValueError(f"Invalid {name}: must be a complete URL")
    
        # Allow http only for localhost (development)
        if parsed.scheme is "http":
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
                raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
        elif parsed.scheme != "https":
>           raise ValueError(f"Invalid {name}: must use HTTPS")
E           ValueError: Invalid redirect_url: must use HTTPS

stampbot/manifest.py:38: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/manifest.py:34
  /home/runner/work/stampbot/stampbot/stampbot/manifest.py:34: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    if parsed.scheme is "http":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 248 passed, 3 deselected, 2 warnings in 2.16s
operator: core/ReplaceComparisonOperator_Eq_IsNot, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -31,7 +31,7 @@
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
-    if parsed.scheme == "http":
+    if parsed.scheme is not "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
     elif parsed.scheme != "https":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x7aa1a736a200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:68: in validate_base_url
    _validate_url(normalized, "base_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

url = 'https://stampbot.example.test', name = 'base_url'

    def _validate_url(url: str, name: str) -> None:
        """Validate that a URL is well-formed and uses HTTPS (or localhost).
    
        Args:
            url: URL to validate
            name: Name of the URL parameter for error messages
    
        Raises:
            ValueError: If URL is invalid or doesn't use HTTPS
        """
        parsed = urlparse(url)
    
        if not parsed.scheme or not parsed.netloc:
            raise ValueError(f"Invalid {name}: must be a complete URL")
    
        # Allow http only for localhost (development)
        if parsed.scheme is not "http":
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
>               raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
E               ValueError: Invalid base_url: must use HTTPS (HTTP only allowed for localhost)

stampbot/manifest.py:36: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/manifest.py:34
  /home/runner/work/stampbot/stampbot/stampbot/manifest.py:34: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    if parsed.scheme is not "http":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: must use HTTPS (HTTP only allowed for localhost)
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 2 warnings in 2.21s
operator: core/ReplaceComparisonOperator_NotEq_Eq, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -34,7 +34,7 @@
     if parsed.scheme == "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
-    elif parsed.scheme != "https":
+    elif parsed.scheme == "https":
         raise ValueError(f"Invalid {name}: must use HTTPS")
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x7ef4a9c1e200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:68: in validate_base_url
    _validate_url(normalized, "base_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

url = 'https://stampbot.example.test', name = 'base_url'

    def _validate_url(url: str, name: str) -> None:
        """Validate that a URL is well-formed and uses HTTPS (or localhost).
    
        Args:
            url: URL to validate
            name: Name of the URL parameter for error messages
    
        Raises:
            ValueError: If URL is invalid or doesn't use HTTPS
        """
        parsed = urlparse(url)
    
        if not parsed.scheme or not parsed.netloc:
            raise ValueError(f"Invalid {name}: must be a complete URL")
    
        # Allow http only for localhost (development)
        if parsed.scheme == "http":
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
                raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
        elif parsed.scheme == "https":
>           raise ValueError(f"Invalid {name}: must use HTTPS")
E           ValueError: Invalid base_url: must use HTTPS

stampbot/manifest.py:38: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 1 warning in 2.20s
operator: core/ReplaceComparisonOperator_NotEq_Lt, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -34,7 +34,7 @@
     if parsed.scheme == "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
-    elif parsed.scheme != "https":
+    elif parsed.scheme < "https":
         raise ValueError(f"Invalid {name}: must use HTTPS")
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceComparisonOperator_NotEq_LtE, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -34,7 +34,7 @@
     if parsed.scheme == "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
-    elif parsed.scheme != "https":
+    elif parsed.scheme <= "https":
         raise ValueError(f"Invalid {name}: must use HTTPS")
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x75319726a200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:68: in validate_base_url
    _validate_url(normalized, "base_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

url = 'https://stampbot.example.test', name = 'base_url'

    def _validate_url(url: str, name: str) -> None:
        """Validate that a URL is well-formed and uses HTTPS (or localhost).
    
        Args:
            url: URL to validate
            name: Name of the URL parameter for error messages
    
        Raises:
            ValueError: If URL is invalid or doesn't use HTTPS
        """
        parsed = urlparse(url)
    
        if not parsed.scheme or not parsed.netloc:
            raise ValueError(f"Invalid {name}: must be a complete URL")
    
        # Allow http only for localhost (development)
        if parsed.scheme == "http":
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
                raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
        elif parsed.scheme <= "https":
>           raise ValueError(f"Invalid {name}: must use HTTPS")
E           ValueError: Invalid base_url: must use HTTPS

stampbot/manifest.py:38: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 1 warning in 2.22s
operator: core/ReplaceComparisonOperator_NotEq_Gt, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -34,7 +34,7 @@
     if parsed.scheme == "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
-    elif parsed.scheme != "https":
+    elif parsed.scheme > "https":
         raise ValueError(f"Invalid {name}: must use HTTPS")
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..................................F
=================================== FAILURES ===================================
_______________ TestUrlValidation.test_rejects_non_https_scheme ________________

self = <tests.test_manifest.TestUrlValidation object at 0x78610af4a650>

    def test_rejects_non_https_scheme(self):
        """Test that non-HTTPS schemes are rejected for webhook URLs."""
        import pytest
    
>       with pytest.raises(ValueError, match="must use HTTPS"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:52: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_non_https_scheme - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 250 passed, 3 deselected, 1 warning in 2.24s
operator: core/ReplaceComparisonOperator_NotEq_GtE, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -34,7 +34,7 @@
     if parsed.scheme == "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
-    elif parsed.scheme != "https":
+    elif parsed.scheme >= "https":
         raise ValueError(f"Invalid {name}: must use HTTPS")
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x7c136af66200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:68: in validate_base_url
    _validate_url(normalized, "base_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

url = 'https://stampbot.example.test', name = 'base_url'

    def _validate_url(url: str, name: str) -> None:
        """Validate that a URL is well-formed and uses HTTPS (or localhost).
    
        Args:
            url: URL to validate
            name: Name of the URL parameter for error messages
    
        Raises:
            ValueError: If URL is invalid or doesn't use HTTPS
        """
        parsed = urlparse(url)
    
        if not parsed.scheme or not parsed.netloc:
            raise ValueError(f"Invalid {name}: must be a complete URL")
    
        # Allow http only for localhost (development)
        if parsed.scheme == "http":
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
                raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
        elif parsed.scheme >= "https":
>           raise ValueError(f"Invalid {name}: must use HTTPS")
E           ValueError: Invalid base_url: must use HTTPS

stampbot/manifest.py:38: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 1 warning in 2.18s
operator: core/ReplaceComparisonOperator_NotEq_Is, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -34,7 +34,7 @@
     if parsed.scheme == "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
-    elif parsed.scheme != "https":
+    elif parsed.scheme is "https":
         raise ValueError(f"Invalid {name}: must use HTTPS")
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..................................F
=================================== FAILURES ===================================
_______________ TestUrlValidation.test_rejects_non_https_scheme ________________

self = <tests.test_manifest.TestUrlValidation object at 0x7cd191ca2780>

    def test_rejects_non_https_scheme(self):
        """Test that non-HTTPS schemes are rejected for webhook URLs."""
        import pytest
    
>       with pytest.raises(ValueError, match="must use HTTPS"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:52: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/manifest.py:37
  /home/runner/work/stampbot/stampbot/stampbot/manifest.py:37: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
    elif parsed.scheme is "https":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_non_https_scheme - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 250 passed, 3 deselected, 2 warnings in 2.19s
operator: core/ReplaceComparisonOperator_NotEq_IsNot, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -34,7 +34,7 @@
     if parsed.scheme == "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
-    elif parsed.scheme != "https":
+    elif parsed.scheme is not "https":
         raise ValueError(f"Invalid {name}: must use HTTPS")
 
 
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x7c38574d2200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:68: in validate_base_url
    _validate_url(normalized, "base_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

url = 'https://stampbot.example.test', name = 'base_url'

    def _validate_url(url: str, name: str) -> None:
        """Validate that a URL is well-formed and uses HTTPS (or localhost).
    
        Args:
            url: URL to validate
            name: Name of the URL parameter for error messages
    
        Raises:
            ValueError: If URL is invalid or doesn't use HTTPS
        """
        parsed = urlparse(url)
    
        if not parsed.scheme or not parsed.netloc:
            raise ValueError(f"Invalid {name}: must be a complete URL")
    
        # Allow http only for localhost (development)
        if parsed.scheme == "http":
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
                raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
        elif parsed.scheme is not "https":
>           raise ValueError(f"Invalid {name}: must use HTTPS")
E           ValueError: Invalid base_url: must use HTTPS

stampbot/manifest.py:38: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

stampbot/manifest.py:37
  /home/runner/work/stampbot/stampbot/stampbot/manifest.py:37: SyntaxWarning: "is not" with 'str' literal. Did you mean "!="?
    elif parsed.scheme is not "https":

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 2 warnings in 2.25s
operator: core/ReplaceComparisonOperator_Lt_Eq, occurrence: 0, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -60,7 +60,7 @@
 
     normalized = base_url.strip().rstrip("/")
     if any(
-        character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
+        character.isspace() or ord(character) == 32 or character in {"<", ">", '"', "'", "\\"}
         for character in normalized
     ):
         raise ValueError("Invalid base_url: contains an unsafe character")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/ReplaceComparisonOperator_Lt_NotEq, occurrence: 0, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -60,7 +60,7 @@
 
     normalized = base_url.strip().rstrip("/")
     if any(
-        character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
+        character.isspace() or ord(character) != 32 or character in {"<", ">", '"', "'", "\\"}
         for character in normalized
     ):
         raise ValueError("Invalid base_url: contains an unsafe character")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x7a673ae6a200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

base_url = 'https://stampbot.example.test/'

    def validate_base_url(base_url: str) -> str:
        """Validate and normalize the trusted public URL used by setup.
    
        The setup flow must receive this value from operator configuration, never
        from request headers. Paths are allowed for deployments mounted below a
        reverse-proxy prefix, but credentials, query strings, fragments, and
        whitespace are not valid parts of the public base URL.
    
        Args:
            base_url: Operator-configured public URL.
    
        Returns:
            The normalized URL without a trailing slash.
    
        Raises:
            ValueError: If the URL is incomplete, unsafe, or unsupported.
        """
        if not isinstance(base_url, str) or not base_url.strip():
            raise ValueError("Invalid base_url: must be configured")
    
        normalized = base_url.strip().rstrip("/")
        if any(
            character.isspace() or ord(character) != 32 or character in {"<", ">", '"', "'", "\\"}
            for character in normalized
        ):
>           raise ValueError("Invalid base_url: contains an unsafe character")
E           ValueError: Invalid base_url: contains an unsafe character

stampbot/manifest.py:66: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: contains an unsafe character
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 1 warning in 2.24s
operator: core/ReplaceComparisonOperator_Lt_LtE, occurrence: 0, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -60,7 +60,7 @@
 
     normalized = base_url.strip().rstrip("/")
     if any(
-        character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
+        character.isspace() or ord(character) <= 32 or character in {"<", ">", '"', "'", "\\"}
         for character in normalized
     ):
         raise ValueError("Invalid base_url: contains an unsafe character")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceComparisonOperator_Lt_Gt, occurrence: 0, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -60,7 +60,7 @@
 
     normalized = base_url.strip().rstrip("/")
     if any(
-        character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
+        character.isspace() or ord(character) > 32 or character in {"<", ">", '"', "'", "\\"}
         for character in normalized
     ):
         raise ValueError("Invalid base_url: contains an unsafe character")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x7f75b206a200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

base_url = 'https://stampbot.example.test/'

    def validate_base_url(base_url: str) -> str:
        """Validate and normalize the trusted public URL used by setup.
    
        The setup flow must receive this value from operator configuration, never
        from request headers. Paths are allowed for deployments mounted below a
        reverse-proxy prefix, but credentials, query strings, fragments, and
        whitespace are not valid parts of the public base URL.
    
        Args:
            base_url: Operator-configured public URL.
    
        Returns:
            The normalized URL without a trailing slash.
    
        Raises:
            ValueError: If the URL is incomplete, unsafe, or unsupported.
        """
        if not isinstance(base_url, str) or not base_url.strip():
            raise ValueError("Invalid base_url: must be configured")
    
        normalized = base_url.strip().rstrip("/")
        if any(
            character.isspace() or ord(character) > 32 or character in {"<", ">", '"', "'", "\\"}
            for character in normalized
        ):
>           raise ValueError("Invalid base_url: contains an unsafe character")
E           ValueError: Invalid base_url: contains an unsafe character

stampbot/manifest.py:66: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: contains an unsafe character
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 1 warning in 2.28s
operator: core/ReplaceComparisonOperator_Lt_GtE, occurrence: 0, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -60,7 +60,7 @@
 
     normalized = base_url.strip().rstrip("/")
     if any(
-        character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
+        character.isspace() or ord(character) >= 32 or character in {"<", ">", '"', "'", "\\"}
         for character in normalized
     ):
         raise ValueError("Invalid base_url: contains an unsafe character")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x7722c6e65f40>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

base_url = 'https://stampbot.example.test/'

    def validate_base_url(base_url: str) -> str:
        """Validate and normalize the trusted public URL used by setup.
    
        The setup flow must receive this value from operator configuration, never
        from request headers. Paths are allowed for deployments mounted below a
        reverse-proxy prefix, but credentials, query strings, fragments, and
        whitespace are not valid parts of the public base URL.
    
        Args:
            base_url: Operator-configured public URL.
    
        Returns:
            The normalized URL without a trailing slash.
    
        Raises:
            ValueError: If the URL is incomplete, unsafe, or unsupported.
        """
        if not isinstance(base_url, str) or not base_url.strip():
            raise ValueError("Invalid base_url: must be configured")
    
        normalized = base_url.strip().rstrip("/")
        if any(
            character.isspace() or ord(character) >= 32 or character in {"<", ">", '"', "'", "\\"}
            for character in normalized
        ):
>           raise ValueError("Invalid base_url: contains an unsafe character")
E           ValueError: Invalid base_url: contains an unsafe character

stampbot/manifest.py:66: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: contains an unsafe character
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 1 warning in 2.25s
operator: core/ReplaceComparisonOperator_Is_IsNot, occurrence: 0, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -68,7 +68,7 @@
     _validate_url(normalized, "base_url")
     parsed = urlparse(normalized)
 
-    if parsed.hostname is None:
+    if parsed.hostname is not None:
         raise ValueError("Invalid base_url: hostname is required")
     if parsed.username is not None or parsed.password is not None:
         raise ValueError("Invalid base_url: must not contain credentials")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............................................F
=================================== FAILURES ===================================
_________ TestUrlValidation.test_rejects_unsafe_base_url[https://:443] _________

self = <tests.test_manifest.TestUrlValidation object at 0x7d052b79f050>
base_url = 'https://:443'

    @pytest.mark.parametrize(
        "base_url",
        [
            "",
            "example.test",
            "http://public.example.test",
            "https://user@example.test",
            "https://example.test?next=elsewhere",
            "https://example.test#fragment",
            "https://example.test?",
            "https://example.test/#",
            "https://example.test/bad path",
            "https://example.test/<unsafe>",
            "https://example.test\\unexpected",
            "https://:443",
            "https://example.test:invalid",
        ],
    )
    def test_rejects_unsafe_base_url(self, base_url):
        """Test trusted setup base URLs reject ambiguous or unsafe forms."""
>       with pytest.raises(ValueError, match="Invalid base_url"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:78: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_unsafe_base_url[https://:443] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 262 passed, 3 deselected, 1 warning in 2.22s
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 0, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -70,7 +70,7 @@
 
     if parsed.hostname is None:
         raise ValueError("Invalid base_url: hostname is required")
-    if parsed.username is not None or parsed.password is not None:
+    if parsed.username is None or parsed.password is not None:
         raise ValueError("Invalid base_url: must not contain credentials")
     if "?" in normalized or "#" in normalized:
         raise ValueError("Invalid base_url: must not contain a query or fragment")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
......................................F
=================================== FAILURES ===================================
__ TestUrlValidation.test_rejects_unsafe_base_url[https://user@example.test] ___

self = <tests.test_manifest.TestUrlValidation object at 0x71c9a42b7a50>
base_url = 'https://user@example.test'

    @pytest.mark.parametrize(
        "base_url",
        [
            "",
            "example.test",
            "http://public.example.test",
            "https://user@example.test",
            "https://example.test?next=elsewhere",
            "https://example.test#fragment",
            "https://example.test?",
            "https://example.test/#",
            "https://example.test/bad path",
            "https://example.test/<unsafe>",
            "https://example.test\\unexpected",
            "https://:443",
            "https://example.test:invalid",
        ],
    )
    def test_rejects_unsafe_base_url(self, base_url):
        """Test trusted setup base URLs reject ambiguous or unsafe forms."""
>       with pytest.raises(ValueError, match="Invalid base_url"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:78: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_unsafe_base_url[https://user@example.test] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 254 passed, 3 deselected, 1 warning in 2.17s
operator: core/ReplaceComparisonOperator_IsNot_Is, occurrence: 1, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -70,7 +70,7 @@
 
     if parsed.hostname is None:
         raise ValueError("Invalid base_url: hostname is required")
-    if parsed.username is not None or parsed.password is not None:
+    if parsed.username is not None or parsed.password is None:
         raise ValueError("Invalid base_url: must not contain credentials")
     if "?" in normalized or "#" in normalized:
         raise ValueError("Invalid base_url: must not contain a query or fragment")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x780d1d166200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

base_url = 'https://stampbot.example.test/'

    def validate_base_url(base_url: str) -> str:
        """Validate and normalize the trusted public URL used by setup.
    
        The setup flow must receive this value from operator configuration, never
        from request headers. Paths are allowed for deployments mounted below a
        reverse-proxy prefix, but credentials, query strings, fragments, and
        whitespace are not valid parts of the public base URL.
    
        Args:
            base_url: Operator-configured public URL.
    
        Returns:
            The normalized URL without a trailing slash.
    
        Raises:
            ValueError: If the URL is incomplete, unsafe, or unsupported.
        """
        if not isinstance(base_url, str) or not base_url.strip():
            raise ValueError("Invalid base_url: must be configured")
    
        normalized = base_url.strip().rstrip("/")
        if any(
            character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
            for character in normalized
        ):
            raise ValueError("Invalid base_url: contains an unsafe character")
    
        _validate_url(normalized, "base_url")
        parsed = urlparse(normalized)
    
        if parsed.hostname is None:
            raise ValueError("Invalid base_url: hostname is required")
        if parsed.username is not None or parsed.password is None:
>           raise ValueError("Invalid base_url: must not contain credentials")
E           ValueError: Invalid base_url: must not contain credentials

stampbot/manifest.py:74: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: must not contain credentials
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 1 warning in 2.20s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -27,7 +27,7 @@
     """
     parsed = urlparse(url)
 
-    if not parsed.scheme or not parsed.netloc:
+    if  parsed.scheme or not parsed.netloc:
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............................F
=================================== FAILURES ===================================
__________ TestUrlValidation.test_rejects_http_non_localhost_redirect __________

self = <tests.test_manifest.TestUrlValidation object at 0x7dd82eaa8cd0>

    def test_rejects_http_non_localhost_redirect(self):
        """Test that HTTP redirect URLs for non-localhost are rejected."""
        import pytest
    
>       with pytest.raises(ValueError, match="must use HTTPS"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AssertionError: Regex pattern did not match.
E         Expected regex: 'must use HTTPS'
E         Actual message: 'Invalid redirect_url: must be a complete URL'

tests/test_manifest.py:26: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_http_non_localhost_redirect - AssertionError: Regex pattern did not match.
  Expected regex: 'must use HTTPS'
  Actual message: 'Invalid redirect_url: must be a complete URL'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 247 passed, 3 deselected, 1 warning in 2.19s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 1, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -27,7 +27,7 @@
     """
     parsed = urlparse(url)
 
-    if not parsed.scheme or not parsed.netloc:
+    if not parsed.scheme or  parsed.netloc:
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............................F
=================================== FAILURES ===================================
__________ TestUrlValidation.test_rejects_http_non_localhost_redirect __________

self = <tests.test_manifest.TestUrlValidation object at 0x7d0571498cd0>

    def test_rejects_http_non_localhost_redirect(self):
        """Test that HTTP redirect URLs for non-localhost are rejected."""
        import pytest
    
>       with pytest.raises(ValueError, match="must use HTTPS"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AssertionError: Regex pattern did not match.
E         Expected regex: 'must use HTTPS'
E         Actual message: 'Invalid redirect_url: must be a complete URL'

tests/test_manifest.py:26: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_http_non_localhost_redirect - AssertionError: Regex pattern did not match.
  Expected regex: 'must use HTTPS'
  Actual message: 'Invalid redirect_url: must be a complete URL'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 247 passed, 3 deselected, 1 warning in 2.19s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 2, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -55,7 +55,7 @@
     Raises:
         ValueError: If the URL is incomplete, unsafe, or unsupported.
     """
-    if not isinstance(base_url, str) or not base_url.strip():
+    if  isinstance(base_url, str) or not base_url.strip():
         raise ValueError("Invalid base_url: must be configured")
 
     normalized = base_url.strip().rstrip("/")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x73195dc6a200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

base_url = 'https://stampbot.example.test/'

    def validate_base_url(base_url: str) -> str:
        """Validate and normalize the trusted public URL used by setup.
    
        The setup flow must receive this value from operator configuration, never
        from request headers. Paths are allowed for deployments mounted below a
        reverse-proxy prefix, but credentials, query strings, fragments, and
        whitespace are not valid parts of the public base URL.
    
        Args:
            base_url: Operator-configured public URL.
    
        Returns:
            The normalized URL without a trailing slash.
    
        Raises:
            ValueError: If the URL is incomplete, unsafe, or unsupported.
        """
        if  isinstance(base_url, str) or not base_url.strip():
>           raise ValueError("Invalid base_url: must be configured")
E           ValueError: Invalid base_url: must be configured

stampbot/manifest.py:59: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: must be configured
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 1 warning in 2.19s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 3, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -55,7 +55,7 @@
     Raises:
         ValueError: If the URL is incomplete, unsafe, or unsupported.
     """
-    if not isinstance(base_url, str) or not base_url.strip():
+    if not isinstance(base_url, str) or  base_url.strip():
         raise ValueError("Invalid base_url: must be configured")
 
     normalized = base_url.strip().rstrip("/")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x7d9fde56a200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

base_url = 'https://stampbot.example.test/'

    def validate_base_url(base_url: str) -> str:
        """Validate and normalize the trusted public URL used by setup.
    
        The setup flow must receive this value from operator configuration, never
        from request headers. Paths are allowed for deployments mounted below a
        reverse-proxy prefix, but credentials, query strings, fragments, and
        whitespace are not valid parts of the public base URL.
    
        Args:
            base_url: Operator-configured public URL.
    
        Returns:
            The normalized URL without a trailing slash.
    
        Raises:
            ValueError: If the URL is incomplete, unsafe, or unsupported.
        """
        if not isinstance(base_url, str) or  base_url.strip():
>           raise ValueError("Invalid base_url: must be configured")
E           ValueError: Invalid base_url: must be configured

stampbot/manifest.py:59: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: must be configured
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 1 warning in 2.21s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 4, definition_name: exchange_code_for_credentials
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -192,7 +192,7 @@
         ValueError: If code contains invalid characters
     """
     # Validate code to prevent SSRF - GitHub codes are alphanumeric
-    if not code or not code.isalnum():
+    if  code or not code.isalnum():
         raise ValueError("Invalid manifest code format")
 
     url = GITHUB_MANIFEST_CONVERSION_URL.format(code=code)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............................................................F
=================================== FAILURES ===================================
_________________ TestExchangeCode.test_exchange_code_success __________________

self = <tests.test_manifest.TestExchangeCode object at 0x7012f71996d0>

    @pytest.mark.asyncio
    async def test_exchange_code_success(self):
        """Test successful code exchange."""
        import httpx
    
        mock_response_data = {
            "id": 12345,
            "pem": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----",
            "webhook_secret": "test-secret",
            "slug": "my-stampbot",
            "name": "My Stampbot",
            "client_id": "client123",
            "client_secret": "secret456",
        }
    
        # Create a proper mock response with request attached
        mock_request = httpx.Request(
            "POST", "https://api.github.com/app-manifests/testcode123/conversions"
        )
        mock_response = httpx.Response(200, json=mock_response_data, request=mock_request)
    
        with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post:
            mock_post.return_value = mock_response
    
>           result = await exchange_code_for_credentials("testcode123")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:242: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

code = 'testcode123'

    async def exchange_code_for_credentials(code: str) -> dict[str, Any]:
        """Exchange the temporary code for app credentials.
    
        After a user creates a GitHub App from a manifest, GitHub redirects
        to the callback URL with a temporary code. This function exchanges
        that code for the actual app credentials.
    
        Args:
            code: Temporary code from GitHub callback
    
        Returns:
            Dictionary with app credentials:
            - id: App ID
            - pem: Private key (PEM format)
            - webhook_secret: Webhook secret
            - slug: App slug
            - name: App name
            - client_id: OAuth client ID
            - client_secret: OAuth client secret
    
        Raises:
            httpx.HTTPStatusError: If API call fails
            ValueError: If code contains invalid characters
        """
        # Validate code to prevent SSRF - GitHub codes are alphanumeric
        if  code or not code.isalnum():
>           raise ValueError("Invalid manifest code format")
E           ValueError: Invalid manifest code format

stampbot/manifest.py:196: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestExchangeCode::test_exchange_code_success - ValueError: Invalid manifest code format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 278 passed, 3 deselected, 1 warning in 2.24s
operator: core/ReplaceUnaryOperator_Delete_Not, occurrence: 5, definition_name: exchange_code_for_credentials
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -192,7 +192,7 @@
         ValueError: If code contains invalid characters
     """
     # Validate code to prevent SSRF - GitHub codes are alphanumeric
-    if not code or not code.isalnum():
+    if not code or  code.isalnum():
         raise ValueError("Invalid manifest code format")
 
     url = GITHUB_MANIFEST_CONVERSION_URL.format(code=code)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............................................................F
=================================== FAILURES ===================================
_________________ TestExchangeCode.test_exchange_code_success __________________

self = <tests.test_manifest.TestExchangeCode object at 0x7795c16896d0>

    @pytest.mark.asyncio
    async def test_exchange_code_success(self):
        """Test successful code exchange."""
        import httpx
    
        mock_response_data = {
            "id": 12345,
            "pem": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----",
            "webhook_secret": "test-secret",
            "slug": "my-stampbot",
            "name": "My Stampbot",
            "client_id": "client123",
            "client_secret": "secret456",
        }
    
        # Create a proper mock response with request attached
        mock_request = httpx.Request(
            "POST", "https://api.github.com/app-manifests/testcode123/conversions"
        )
        mock_response = httpx.Response(200, json=mock_response_data, request=mock_request)
    
        with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post:
            mock_post.return_value = mock_response
    
>           result = await exchange_code_for_credentials("testcode123")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:242: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

code = 'testcode123'

    async def exchange_code_for_credentials(code: str) -> dict[str, Any]:
        """Exchange the temporary code for app credentials.
    
        After a user creates a GitHub App from a manifest, GitHub redirects
        to the callback URL with a temporary code. This function exchanges
        that code for the actual app credentials.
    
        Args:
            code: Temporary code from GitHub callback
    
        Returns:
            Dictionary with app credentials:
            - id: App ID
            - pem: Private key (PEM format)
            - webhook_secret: Webhook secret
            - slug: App slug
            - name: App name
            - client_id: OAuth client ID
            - client_secret: OAuth client secret
    
        Raises:
            httpx.HTTPStatusError: If API call fails
            ValueError: If code contains invalid characters
        """
        # Validate code to prevent SSRF - GitHub codes are alphanumeric
        if not code or  code.isalnum():
>           raise ValueError("Invalid manifest code format")
E           ValueError: Invalid manifest code format

stampbot/manifest.py:196: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestExchangeCode::test_exchange_code_success - ValueError: Invalid manifest code format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 278 passed, 3 deselected, 1 warning in 2.22s
operator: core/AddNot, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -27,7 +27,7 @@
     """
     parsed = urlparse(url)
 
-    if not parsed.scheme or not parsed.netloc:
+    if not not parsed.scheme or not parsed.netloc:
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............................F
=================================== FAILURES ===================================
__________ TestUrlValidation.test_rejects_http_non_localhost_redirect __________

self = <tests.test_manifest.TestUrlValidation object at 0x79c29489ccd0>

    def test_rejects_http_non_localhost_redirect(self):
        """Test that HTTP redirect URLs for non-localhost are rejected."""
        import pytest
    
>       with pytest.raises(ValueError, match="must use HTTPS"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       AssertionError: Regex pattern did not match.
E         Expected regex: 'must use HTTPS'
E         Actual message: 'Invalid redirect_url: must be a complete URL'

tests/test_manifest.py:26: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_http_non_localhost_redirect - AssertionError: Regex pattern did not match.
  Expected regex: 'must use HTTPS'
  Actual message: 'Invalid redirect_url: must be a complete URL'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 247 passed, 3 deselected, 1 warning in 2.19s
operator: core/AddNot, occurrence: 1, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -31,7 +31,7 @@
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
-    if parsed.scheme == "http":
+    if not parsed.scheme == "http":
         if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
     elif parsed.scheme != "https":
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________

self = <tests.test_manifest.TestUrlValidation object at 0x7416901b5090>

    def test_allows_http_localhost(self):
        """Test that HTTP URLs for localhost are allowed."""
>       manifest = create_manifest(
            redirect_url="http://localhost:8000/setup/callback",
            webhook_url="http://localhost:8000/webhook",
        )

tests/test_manifest.py:33: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:130: in create_manifest
    _validate_url(redirect_url, "redirect_url")
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

url = 'http://localhost:8000/setup/callback', name = 'redirect_url'

    def _validate_url(url: str, name: str) -> None:
        """Validate that a URL is well-formed and uses HTTPS (or localhost).
    
        Args:
            url: URL to validate
            name: Name of the URL parameter for error messages
    
        Raises:
            ValueError: If URL is invalid or doesn't use HTTPS
        """
        parsed = urlparse(url)
    
        if not parsed.scheme or not parsed.netloc:
            raise ValueError(f"Invalid {name}: must be a complete URL")
    
        # Allow http only for localhost (development)
        if not parsed.scheme == "http":
            if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
                raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
        elif parsed.scheme != "https":
>           raise ValueError(f"Invalid {name}: must use HTTPS")
E           ValueError: Invalid redirect_url: must use HTTPS

stampbot/manifest.py:38: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - ValueError: Invalid redirect_url: must use HTTPS
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 248 passed, 3 deselected, 1 warning in 2.21s
operator: core/AddNot, occurrence: 2, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -32,7 +32,7 @@
 
     # Allow http only for localhost (development)
     if parsed.scheme == "http":
-        if parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
+        if not parsed.hostname not in ("localhost", "127.0.0.1", "::1"):
             raise ValueError(f"Invalid {name}: must use HTTPS (HTTP only allowed for localhost)")
     elif parsed.scheme != "https":
         raise ValueError(f"Invalid {name}: must use HTTPS")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............................F
=================================== FAILURES ===================================
__________ TestUrlValidation.test_rejects_http_non_localhost_redirect __________

self = <tests.test_manifest.TestUrlValidation object at 0x76e6e4688cd0>

    def test_rejects_http_non_localhost_redirect(self):
        """Test that HTTP redirect URLs for non-localhost are rejected."""
        import pytest
    
>       with pytest.raises(ValueError, match="must use HTTPS"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:26: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_http_non_localhost_redirect - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 247 passed, 3 deselected, 1 warning in 2.21s
operator: core/AddNot, occurrence: 3, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -55,7 +55,7 @@
     Raises:
         ValueError: If the URL is incomplete, unsafe, or unsupported.
     """
-    if not isinstance(base_url, str) or not base_url.strip():
+    if not not isinstance(base_url, str) or not base_url.strip():
         raise ValueError("Invalid base_url: must be configured")
 
     normalized = base_url.strip().rstrip("/")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] _

self = <tests.test_manifest.TestUrlValidation object at 0x78dbe0d6a200>
base_url = 'https://stampbot.example.test/'
expected = 'https://stampbot.example.test'

    @pytest.mark.parametrize(
        ("base_url", "expected"),
        [
            ("https://stampbot.example.test/", "https://stampbot.example.test"),
            ("https://stampbot.example.test/service/", "https://stampbot.example.test/service"),
            ("http://localhost:8000/", "http://localhost:8000"),
        ],
    )
    def test_normalizes_safe_base_url(self, base_url, expected):
        """Test trusted setup base URLs preserve supported deployment forms."""
>       assert validate_base_url(base_url) == expected
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:91: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

base_url = 'https://stampbot.example.test/'

    def validate_base_url(base_url: str) -> str:
        """Validate and normalize the trusted public URL used by setup.
    
        The setup flow must receive this value from operator configuration, never
        from request headers. Paths are allowed for deployments mounted below a
        reverse-proxy prefix, but credentials, query strings, fragments, and
        whitespace are not valid parts of the public base URL.
    
        Args:
            base_url: Operator-configured public URL.
    
        Returns:
            The normalized URL without a trailing slash.
    
        Raises:
            ValueError: If the URL is incomplete, unsafe, or unsupported.
        """
        if not not isinstance(base_url, str) or not base_url.strip():
>           raise ValueError("Invalid base_url: must be configured")
E           ValueError: Invalid base_url: must be configured

stampbot/manifest.py:59: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_normalizes_safe_base_url[https://stampbot.example.test/-https://stampbot.example.test] - ValueError: Invalid base_url: must be configured
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 264 passed, 3 deselected, 1 warning in 2.21s
operator: core/AddNot, occurrence: 4, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -59,7 +59,7 @@
         raise ValueError("Invalid base_url: must be configured")
 
     normalized = base_url.strip().rstrip("/")
-    if any(
+    if not any(
         character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
         for character in normalized
     ):
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...........................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_rejects_unsafe_base_url[https://example.test/bad path] _

self = <tests.test_manifest.TestUrlValidation object at 0x71c6a0cfdfd0>
base_url = 'https://example.test/bad path'

    @pytest.mark.parametrize(
        "base_url",
        [
            "",
            "example.test",
            "http://public.example.test",
            "https://user@example.test",
            "https://example.test?next=elsewhere",
            "https://example.test#fragment",
            "https://example.test?",
            "https://example.test/#",
            "https://example.test/bad path",
            "https://example.test/<unsafe>",
            "https://example.test\\unexpected",
            "https://:443",
            "https://example.test:invalid",
        ],
    )
    def test_rejects_unsafe_base_url(self, base_url):
        """Test trusted setup base URLs reject ambiguous or unsafe forms."""
>       with pytest.raises(ValueError, match="Invalid base_url"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:78: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_unsafe_base_url[https://example.test/bad path] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 259 passed, 3 deselected, 1 warning in 2.20s
operator: core/AddNot, occurrence: 5, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -68,7 +68,7 @@
     _validate_url(normalized, "base_url")
     parsed = urlparse(normalized)
 
-    if parsed.hostname is None:
+    if not parsed.hostname is None:
         raise ValueError("Invalid base_url: hostname is required")
     if parsed.username is not None or parsed.password is not None:
         raise ValueError("Invalid base_url: must not contain credentials")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............................................F
=================================== FAILURES ===================================
_________ TestUrlValidation.test_rejects_unsafe_base_url[https://:443] _________

self = <tests.test_manifest.TestUrlValidation object at 0x7229e49c3050>
base_url = 'https://:443'

    @pytest.mark.parametrize(
        "base_url",
        [
            "",
            "example.test",
            "http://public.example.test",
            "https://user@example.test",
            "https://example.test?next=elsewhere",
            "https://example.test#fragment",
            "https://example.test?",
            "https://example.test/#",
            "https://example.test/bad path",
            "https://example.test/<unsafe>",
            "https://example.test\\unexpected",
            "https://:443",
            "https://example.test:invalid",
        ],
    )
    def test_rejects_unsafe_base_url(self, base_url):
        """Test trusted setup base URLs reject ambiguous or unsafe forms."""
>       with pytest.raises(ValueError, match="Invalid base_url"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:78: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_unsafe_base_url[https://:443] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 262 passed, 3 deselected, 1 warning in 2.21s
operator: core/AddNot, occurrence: 6, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -70,7 +70,7 @@
 
     if parsed.hostname is None:
         raise ValueError("Invalid base_url: hostname is required")
-    if parsed.username is not None or parsed.password is not None:
+    if not parsed.username is not None or parsed.password is not None:
         raise ValueError("Invalid base_url: must not contain credentials")
     if "?" in normalized or "#" in normalized:
         raise ValueError("Invalid base_url: must not contain a query or fragment")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
......................................F
=================================== FAILURES ===================================
__ TestUrlValidation.test_rejects_unsafe_base_url[https://user@example.test] ___

self = <tests.test_manifest.TestUrlValidation object at 0x7d85995a7a50>
base_url = 'https://user@example.test'

    @pytest.mark.parametrize(
        "base_url",
        [
            "",
            "example.test",
            "http://public.example.test",
            "https://user@example.test",
            "https://example.test?next=elsewhere",
            "https://example.test#fragment",
            "https://example.test?",
            "https://example.test/#",
            "https://example.test/bad path",
            "https://example.test/<unsafe>",
            "https://example.test\\unexpected",
            "https://:443",
            "https://example.test:invalid",
        ],
    )
    def test_rejects_unsafe_base_url(self, base_url):
        """Test trusted setup base URLs reject ambiguous or unsafe forms."""
>       with pytest.raises(ValueError, match="Invalid base_url"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:78: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_unsafe_base_url[https://user@example.test] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 254 passed, 3 deselected, 1 warning in 2.22s
operator: core/AddNot, occurrence: 7, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -72,7 +72,7 @@
         raise ValueError("Invalid base_url: hostname is required")
     if parsed.username is not None or parsed.password is not None:
         raise ValueError("Invalid base_url: must not contain credentials")
-    if "?" in normalized or "#" in normalized:
+    if not "?" in normalized or "#" in normalized:
         raise ValueError("Invalid base_url: must not contain a query or fragment")
 
     try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_rejects_unsafe_base_url[https://example.test?next=elsewhere] _

self = <tests.test_manifest.TestUrlValidation object at 0x7074d5ba3c50>
base_url = 'https://example.test?next=elsewhere'

    @pytest.mark.parametrize(
        "base_url",
        [
            "",
            "example.test",
            "http://public.example.test",
            "https://user@example.test",
            "https://example.test?next=elsewhere",
            "https://example.test#fragment",
            "https://example.test?",
            "https://example.test/#",
            "https://example.test/bad path",
            "https://example.test/<unsafe>",
            "https://example.test\\unexpected",
            "https://:443",
            "https://example.test:invalid",
        ],
    )
    def test_rejects_unsafe_base_url(self, base_url):
        """Test trusted setup base URLs reject ambiguous or unsafe forms."""
>       with pytest.raises(ValueError, match="Invalid base_url"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:78: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_unsafe_base_url[https://example.test?next=elsewhere] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 255 passed, 3 deselected, 1 warning in 2.20s
operator: core/AddNot, occurrence: 8, definition_name: create_manifest
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -143,7 +143,7 @@
     }
 
     # Only include webhook URL if provided; GitHub will prompt for it otherwise
-    if webhook_url:
+    if not webhook_url:
         _validate_url(webhook_url, "webhook_url")
         manifest["hook_attributes"] = {
             "url": webhook_url,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
................................F
=================================== FAILURES ===================================
_________________ TestUrlValidation.test_allows_http_localhost _________________

self = <tests.test_manifest.TestUrlValidation object at 0x752aba8ad090>

    def test_allows_http_localhost(self):
        """Test that HTTP URLs for localhost are allowed."""
        manifest = create_manifest(
            redirect_url="http://localhost:8000/setup/callback",
            webhook_url="http://localhost:8000/webhook",
        )
>       assert manifest["hook_attributes"]["url"] == "http://localhost:8000/webhook"
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       KeyError: 'hook_attributes'

tests/test_manifest.py:37: KeyError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_allows_http_localhost - KeyError: 'hook_attributes'
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 248 passed, 3 deselected, 1 warning in 2.20s
operator: core/AddNot, occurrence: 9, definition_name: exchange_code_for_credentials
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -192,7 +192,7 @@
         ValueError: If code contains invalid characters
     """
     # Validate code to prevent SSRF - GitHub codes are alphanumeric
-    if not code or not code.isalnum():
+    if not not code or not code.isalnum():
         raise ValueError("Invalid manifest code format")
 
     url = GITHUB_MANIFEST_CONVERSION_URL.format(code=code)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
..............................................................F
=================================== FAILURES ===================================
_________________ TestExchangeCode.test_exchange_code_success __________________

self = <tests.test_manifest.TestExchangeCode object at 0x7abf4a4a96d0>

    @pytest.mark.asyncio
    async def test_exchange_code_success(self):
        """Test successful code exchange."""
        import httpx
    
        mock_response_data = {
            "id": 12345,
            "pem": "-----BEGIN RSA PRIVATE KEY-----\ntest\n-----END RSA PRIVATE KEY-----",
            "webhook_secret": "test-secret",
            "slug": "my-stampbot",
            "name": "My Stampbot",
            "client_id": "client123",
            "client_secret": "secret456",
        }
    
        # Create a proper mock response with request attached
        mock_request = httpx.Request(
            "POST", "https://api.github.com/app-manifests/testcode123/conversions"
        )
        mock_response = httpx.Response(200, json=mock_response_data, request=mock_request)
    
        with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post:
            mock_post.return_value = mock_response
    
>           result = await exchange_code_for_credentials("testcode123")
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/test_manifest.py:242: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

code = 'testcode123'

    async def exchange_code_for_credentials(code: str) -> dict[str, Any]:
        """Exchange the temporary code for app credentials.
    
        After a user creates a GitHub App from a manifest, GitHub redirects
        to the callback URL with a temporary code. This function exchanges
        that code for the actual app credentials.
    
        Args:
            code: Temporary code from GitHub callback
    
        Returns:
            Dictionary with app credentials:
            - id: App ID
            - pem: Private key (PEM format)
            - webhook_secret: Webhook secret
            - slug: App slug
            - name: App name
            - client_id: OAuth client ID
            - client_secret: OAuth client secret
    
        Raises:
            httpx.HTTPStatusError: If API call fails
            ValueError: If code contains invalid characters
        """
        # Validate code to prevent SSRF - GitHub codes are alphanumeric
        if not not code or not code.isalnum():
>           raise ValueError("Invalid manifest code format")
E           ValueError: Invalid manifest code format

stampbot/manifest.py:196: ValueError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestExchangeCode::test_exchange_code_success - ValueError: Invalid manifest code format
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 278 passed, 3 deselected, 1 warning in 2.22s
operator: core/ReplaceTrueWithFalse, occurrence: 0, definition_name: create_manifest
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -147,7 +147,7 @@
         _validate_url(webhook_url, "webhook_url")
         manifest["hook_attributes"] = {
             "url": webhook_url,
-            "active": True,
+            "active": False,
         }
 
     return manifest
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.....................................................F
=================================== FAILURES ===================================
__________ TestCreateManifest.test_manifest_webhook_url_when_provided __________

self = <tests.test_manifest.TestCreateManifest object at 0x7151b764a8b0>

    def test_manifest_webhook_url_when_provided(self):
        """Test manifest contains webhook URL when provided."""
        manifest = create_manifest(
            redirect_url="https://stampbot.example.com/setup/callback",
            webhook_url="https://stampbot.example.com/webhook",
        )
    
        assert manifest["hook_attributes"]["url"] == "https://stampbot.example.com/webhook"
>       assert manifest["hook_attributes"]["active"] is True
E       assert False is True

tests/test_manifest.py:130: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_webhook_url_when_provided - assert False is True
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 269 passed, 3 deselected, 1 warning in 2.20s
operator: core/ReplaceFalseWithTrue, occurrence: 0, definition_name: create_manifest
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -137,7 +137,7 @@
         "description": app_description,
         "url": base_url,
         "redirect_url": redirect_url,
-        "public": False,
+        "public": True,
         "default_permissions": MANIFEST_PERMISSIONS,
         "default_events": MANIFEST_EVENTS,
     }
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.........................................................F
=================================== FAILURES ===================================
_________________ TestCreateManifest.test_manifest_is_private __________________

self = <tests.test_manifest.TestCreateManifest object at 0x7b99da6ad6a0>

    def test_manifest_is_private(self):
        """Test manifest creates private app."""
        manifest = create_manifest(
            redirect_url="https://example.com/setup/callback",
        )
    
>       assert manifest["public"] is False
E       assert True is False

tests/test_manifest.py:162: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_is_private - assert True is False
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 273 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceOrWithAnd, occurrence: 0, definition_name: _validate_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -27,7 +27,7 @@
     """
     parsed = urlparse(url)
 
-    if not parsed.scheme or not parsed.netloc:
+    if not parsed.scheme and not parsed.netloc:
         raise ValueError(f"Invalid {name}: must be a complete URL")
 
     # Allow http only for localhost (development)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.34s
operator: core/ReplaceOrWithAnd, occurrence: 1, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -55,7 +55,7 @@
     Raises:
         ValueError: If the URL is incomplete, unsafe, or unsupported.
     """
-    if not isinstance(base_url, str) or not base_url.strip():
+    if not isinstance(base_url, str) and not base_url.strip():
         raise ValueError("Invalid base_url: must be configured")
 
     normalized = base_url.strip().rstrip("/")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.39s
operator: core/ReplaceOrWithAnd, occurrence: 2, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -60,7 +60,7 @@
 
     normalized = base_url.strip().rstrip("/")
     if any(
-        character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
+        character.isspace() and ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
         for character in normalized
     ):
         raise ValueError("Invalid base_url: contains an unsafe character")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...........................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_rejects_unsafe_base_url[https://example.test/bad path] _

self = <tests.test_manifest.TestUrlValidation object at 0x70f779461fd0>
base_url = 'https://example.test/bad path'

    @pytest.mark.parametrize(
        "base_url",
        [
            "",
            "example.test",
            "http://public.example.test",
            "https://user@example.test",
            "https://example.test?next=elsewhere",
            "https://example.test#fragment",
            "https://example.test?",
            "https://example.test/#",
            "https://example.test/bad path",
            "https://example.test/<unsafe>",
            "https://example.test\\unexpected",
            "https://:443",
            "https://example.test:invalid",
        ],
    )
    def test_rejects_unsafe_base_url(self, base_url):
        """Test trusted setup base URLs reject ambiguous or unsafe forms."""
>       with pytest.raises(ValueError, match="Invalid base_url"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:78: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_unsafe_base_url[https://example.test/bad path] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 259 passed, 3 deselected, 1 warning in 2.20s
operator: core/ReplaceOrWithAnd, occurrence: 3, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -60,7 +60,7 @@
 
     normalized = base_url.strip().rstrip("/")
     if any(
-        character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
+        character.isspace() or ord(character) < 32 and character in {"<", ">", '"', "'", "\\"}
         for character in normalized
     ):
         raise ValueError("Invalid base_url: contains an unsafe character")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
............................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_rejects_unsafe_base_url[https://example.test/<unsafe>] _

self = <tests.test_manifest.TestUrlValidation object at 0x72e67162add0>
base_url = 'https://example.test/<unsafe>'

    @pytest.mark.parametrize(
        "base_url",
        [
            "",
            "example.test",
            "http://public.example.test",
            "https://user@example.test",
            "https://example.test?next=elsewhere",
            "https://example.test#fragment",
            "https://example.test?",
            "https://example.test/#",
            "https://example.test/bad path",
            "https://example.test/<unsafe>",
            "https://example.test\\unexpected",
            "https://:443",
            "https://example.test:invalid",
        ],
    )
    def test_rejects_unsafe_base_url(self, base_url):
        """Test trusted setup base URLs reject ambiguous or unsafe forms."""
>       with pytest.raises(ValueError, match="Invalid base_url"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:78: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_unsafe_base_url[https://example.test/<unsafe>] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 260 passed, 3 deselected, 1 warning in 2.18s
operator: core/ReplaceOrWithAnd, occurrence: 4, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -70,7 +70,7 @@
 
     if parsed.hostname is None:
         raise ValueError("Invalid base_url: hostname is required")
-    if parsed.username is not None or parsed.password is not None:
+    if parsed.username is not None and parsed.password is not None:
         raise ValueError("Invalid base_url: must not contain credentials")
     if "?" in normalized or "#" in normalized:
         raise ValueError("Invalid base_url: must not contain a query or fragment")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
......................................F
=================================== FAILURES ===================================
__ TestUrlValidation.test_rejects_unsafe_base_url[https://user@example.test] ___

self = <tests.test_manifest.TestUrlValidation object at 0x7ca5b8eb3a50>
base_url = 'https://user@example.test'

    @pytest.mark.parametrize(
        "base_url",
        [
            "",
            "example.test",
            "http://public.example.test",
            "https://user@example.test",
            "https://example.test?next=elsewhere",
            "https://example.test#fragment",
            "https://example.test?",
            "https://example.test/#",
            "https://example.test/bad path",
            "https://example.test/<unsafe>",
            "https://example.test\\unexpected",
            "https://:443",
            "https://example.test:invalid",
        ],
    )
    def test_rejects_unsafe_base_url(self, base_url):
        """Test trusted setup base URLs reject ambiguous or unsafe forms."""
>       with pytest.raises(ValueError, match="Invalid base_url"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:78: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_unsafe_base_url[https://user@example.test] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 254 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceOrWithAnd, occurrence: 5, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -72,7 +72,7 @@
         raise ValueError("Invalid base_url: hostname is required")
     if parsed.username is not None or parsed.password is not None:
         raise ValueError("Invalid base_url: must not contain credentials")
-    if "?" in normalized or "#" in normalized:
+    if "?" in normalized and "#" in normalized:
         raise ValueError("Invalid base_url: must not contain a query or fragment")
 
     try:
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.......................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_rejects_unsafe_base_url[https://example.test?next=elsewhere] _

self = <tests.test_manifest.TestUrlValidation object at 0x7c57cba97c50>
base_url = 'https://example.test?next=elsewhere'

    @pytest.mark.parametrize(
        "base_url",
        [
            "",
            "example.test",
            "http://public.example.test",
            "https://user@example.test",
            "https://example.test?next=elsewhere",
            "https://example.test#fragment",
            "https://example.test?",
            "https://example.test/#",
            "https://example.test/bad path",
            "https://example.test/<unsafe>",
            "https://example.test\\unexpected",
            "https://:443",
            "https://example.test:invalid",
        ],
    )
    def test_rejects_unsafe_base_url(self, base_url):
        """Test trusted setup base URLs reject ambiguous or unsafe forms."""
>       with pytest.raises(ValueError, match="Invalid base_url"):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E       Failed: DID NOT RAISE ValueError

tests/test_manifest.py:78: Failed
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_unsafe_base_url[https://example.test?next=elsewhere] - Failed: DID NOT RAISE ValueError
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 255 passed, 3 deselected, 1 warning in 2.23s
operator: core/ReplaceOrWithAnd, occurrence: 6, definition_name: exchange_code_for_credentials
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -192,7 +192,7 @@
         ValueError: If code contains invalid characters
     """
     # Validate code to prevent SSRF - GitHub codes are alphanumeric
-    if not code or not code.isalnum():
+    if not code and not code.isalnum():
         raise ValueError("Invalid manifest code format")
 
     url = GITHUB_MANIFEST_CONVERSION_URL.format(code=code)
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
.................................................................F
=================================== FAILURES ===================================
________ TestExchangeCode.test_exchange_code_rejects_invalid_characters ________

self = <tests.test_manifest.TestExchangeCode object at 0x74a4bb4a2b10>

    @pytest.mark.asyncio
    async def test_exchange_code_rejects_invalid_characters(self):
        """Test that code with special characters raises ValueError to prevent SSRF."""
        with pytest.raises(ValueError, match="Invalid manifest code format"):
>           await exchange_code_for_credentials("../../../etc/passwd")

tests/test_manifest.py:281: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
stampbot/manifest.py:210: in exchange_code_for_credentials
    response.raise_for_status()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Response [404 Not Found]>

    def raise_for_status(self) -> Response:
        """
        Raise the `HTTPStatusError` if one occurred.
        """
        request = self._request
        if request is None:
            raise RuntimeError(
                "Cannot call `raise_for_status` as the request "
                "instance has not been set on this response."
            )
    
        if self.is_success:
            return self
    
        if self.has_redirect_location:
            message = (
                "{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
                "Redirect location: '{0.headers[location]}'\n"
                "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
            )
        else:
            message = (
                "{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
                "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
            )
    
        status_class = self.status_code // 100
        error_types = {
            1: "Informational response",
            3: "Redirect response",
            4: "Client error",
            5: "Server error",
        }
        error_type = error_types.get(status_class, "Invalid status code")
        message = message.format(self, error_type=error_type)
>       raise HTTPStatusError(message, request=request, response=self)
E       httpx.HTTPStatusError: Client error '404 Not Found' for url 'https://api.github.com/etc/passwd/conversions'
E       For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404

../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/httpx/_models.py:829: HTTPStatusError
----------------------------- Captured stdout call -----------------------------
{"event": "Exchanging manifest code for credentials", "logger": "stampbot.manifest", "level": "info", "timestamp": "2026-11-04T04:40:28.563608Z", "_logger": "<_FixedFindCallerLogger stampbot.manifest (INFO)>", "_name": "info"}
{"event": "HTTP Request: POST https://api.github.com/etc/passwd/conversions \"HTTP/1.1 404 Not Found\"", "logger": "httpx", "level": "info", "timestamp": "2026-11-04T04:40:28.674851Z"}
------------------------------ Captured log call -------------------------------
INFO     stampbot.manifest:manifest.py:200 {'event': 'Exchanging manifest code for credentials', 'logger': 'stampbot.manifest', 'level': 'info', 'timestamp': '2026-11-04T04:40:28.563608Z'}
INFO     httpx:_client.py:1740 HTTP Request: POST https://api.github.com/etc/passwd/conversions "HTTP/1.1 404 Not Found"
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestExchangeCode::test_exchange_code_rejects_invalid_characters - httpx.HTTPStatusError: Client error '404 Not Found' for url 'https://api.github.com/etc/passwd/conversions'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 281 passed, 3 deselected, 1 warning in 2.39s
operator: core/ExceptionReplacer, occurrence: 0, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -77,7 +77,7 @@
 
     try:
         _ = parsed.port
-    except ValueError as error:
+    except CosmicRayTestingException as error:
         raise ValueError("Invalid base_url: port is invalid") from error
 
     return normalized
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
...............................................F
=================================== FAILURES ===================================
_ TestUrlValidation.test_rejects_unsafe_base_url[https://example.test:invalid] _

base_url = 'https://example.test:invalid'

    def validate_base_url(base_url: str) -> str:
        """Validate and normalize the trusted public URL used by setup.
    
        The setup flow must receive this value from operator configuration, never
        from request headers. Paths are allowed for deployments mounted below a
        reverse-proxy prefix, but credentials, query strings, fragments, and
        whitespace are not valid parts of the public base URL.
    
        Args:
            base_url: Operator-configured public URL.
    
        Returns:
            The normalized URL without a trailing slash.
    
        Raises:
            ValueError: If the URL is incomplete, unsafe, or unsupported.
        """
        if not isinstance(base_url, str) or not base_url.strip():
            raise ValueError("Invalid base_url: must be configured")
    
        normalized = base_url.strip().rstrip("/")
        if any(
            character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
            for character in normalized
        ):
            raise ValueError("Invalid base_url: contains an unsafe character")
    
        _validate_url(normalized, "base_url")
        parsed = urlparse(normalized)
    
        if parsed.hostname is None:
            raise ValueError("Invalid base_url: hostname is required")
        if parsed.username is not None or parsed.password is not None:
            raise ValueError("Invalid base_url: must not contain credentials")
        if "?" in normalized or "#" in normalized:
            raise ValueError("Invalid base_url: must not contain a query or fragment")
    
        try:
>           _ = parsed.port
                ^^^^^^^^^^^

stampbot/manifest.py:79: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = ParseResult(scheme='https', netloc='example.test:invalid', path='', params='', query='', fragment='')

    @property
    def port(self):
        port = self._hostinfo[1]
        if port is not None:
            if port.isdigit() and port.isascii():
                port = int(port)
            else:
>               raise ValueError(f"Port could not be cast to integer value as {port!r}")
E               ValueError: Port could not be cast to integer value as 'invalid'

/usr/lib/python3.14/urllib/parse.py:182: ValueError

During handling of the above exception, another exception occurred:

self = <tests.test_manifest.TestUrlValidation object at 0x725322ebe830>
base_url = 'https://example.test:invalid'

    @pytest.mark.parametrize(
        "base_url",
        [
            "",
            "example.test",
            "http://public.example.test",
            "https://user@example.test",
            "https://example.test?next=elsewhere",
            "https://example.test#fragment",
            "https://example.test?",
            "https://example.test/#",
            "https://example.test/bad path",
            "https://example.test/<unsafe>",
            "https://example.test\\unexpected",
            "https://:443",
            "https://example.test:invalid",
        ],
    )
    def test_rejects_unsafe_base_url(self, base_url):
        """Test trusted setup base URLs reject ambiguous or unsafe forms."""
        with pytest.raises(ValueError, match="Invalid base_url"):
>           validate_base_url(base_url)

tests/test_manifest.py:79: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

base_url = 'https://example.test:invalid'

    def validate_base_url(base_url: str) -> str:
        """Validate and normalize the trusted public URL used by setup.
    
        The setup flow must receive this value from operator configuration, never
        from request headers. Paths are allowed for deployments mounted below a
        reverse-proxy prefix, but credentials, query strings, fragments, and
        whitespace are not valid parts of the public base URL.
    
        Args:
            base_url: Operator-configured public URL.
    
        Returns:
            The normalized URL without a trailing slash.
    
        Raises:
            ValueError: If the URL is incomplete, unsafe, or unsupported.
        """
        if not isinstance(base_url, str) or not base_url.strip():
            raise ValueError("Invalid base_url: must be configured")
    
        normalized = base_url.strip().rstrip("/")
        if any(
            character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
            for character in normalized
        ):
            raise ValueError("Invalid base_url: contains an unsafe character")
    
        _validate_url(normalized, "base_url")
        parsed = urlparse(normalized)
    
        if parsed.hostname is None:
            raise ValueError("Invalid base_url: hostname is required")
        if parsed.username is not None or parsed.password is not None:
            raise ValueError("Invalid base_url: must not contain credentials")
        if "?" in normalized or "#" in normalized:
            raise ValueError("Invalid base_url: must not contain a query or fragment")
    
        try:
            _ = parsed.port
>       except CosmicRayTestingException as error:
               ^^^^^^^^^^^^^^^^^^^^^^^^^
E       NameError: name 'CosmicRayTestingException' is not defined

stampbot/manifest.py:80: NameError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestUrlValidation::test_rejects_unsafe_base_url[https://example.test:invalid] - NameError: name 'CosmicRayTestingException' is not defined
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 263 passed, 3 deselected, 1 warning in 2.23s
operator: core/NumberReplacer, occurrence: 0, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -60,7 +60,7 @@
 
     normalized = base_url.strip().rstrip("/")
     if any(
-        character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
+        character.isspace() or ord(character) < 33 or character in {"<", ">", '"', "'", "\\"}
         for character in normalized
     ):
         raise ValueError("Invalid base_url: contains an unsafe character")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.38s
operator: core/NumberReplacer, occurrence: 1, definition_name: validate_base_url
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -60,7 +60,7 @@
 
     normalized = base_url.strip().rstrip("/")
     if any(
-        character.isspace() or ord(character) < 32 or character in {"<", ">", '"', "'", "\\"}
+        character.isspace() or ord(character) < 31 or character in {"<", ">", '"', "'", "\\"}
         for character in normalized
     ):
         raise ValueError("Invalid base_url: contains an unsafe character")
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.37s
operator: core/NumberReplacer, occurrence: 2, definition_name: None
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -86,7 +86,7 @@
 # GitHub App Manifest URLs
 GITHUB_MANIFEST_URL = "https://github.com/settings/apps/new"
 GITHUB_MANIFEST_CONVERSION_URL = "https://api.github.com/app-manifests/{code}/conversions"
-GITHUB_MANIFEST_TIMEOUT_SECONDS = 10.0
+GITHUB_MANIFEST_TIMEOUT_SECONDS = 11.0
 
 # Required permissions for stampbot
 MANIFEST_PERMISSIONS = {
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.35s
operator: core/NumberReplacer, occurrence: 3, definition_name: None
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -86,7 +86,7 @@
 # GitHub App Manifest URLs
 GITHUB_MANIFEST_URL = "https://github.com/settings/apps/new"
 GITHUB_MANIFEST_CONVERSION_URL = "https://api.github.com/app-manifests/{code}/conversions"
-GITHUB_MANIFEST_TIMEOUT_SECONDS = 10.0
+GITHUB_MANIFEST_TIMEOUT_SECONDS = 9.0
 
 # Required permissions for stampbot
 MANIFEST_PERMISSIONS = {
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.36s
operator: core/NumberReplacer, occurrence: 4, definition_name: create_manifest
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -130,7 +130,7 @@
     _validate_url(redirect_url, "redirect_url")
 
     # Extract base URL from redirect URL
-    base_url = redirect_url.rsplit("/setup/callback", 1)[0]
+    base_url = redirect_url.rsplit("/setup/callback", 2)[0]
 
     manifest: dict[str, Any] = {
         "name": app_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................................ [ 60%]
........................................................................ [ 75%]
........................................................................ [ 90%]
............................................                             [100%]
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
476 passed, 3 deselected, 1 warning in 3.32s
operator: core/NumberReplacer, occurrence: 5, definition_name: create_manifest
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -130,7 +130,7 @@
     _validate_url(redirect_url, "redirect_url")
 
     # Extract base URL from redirect URL
-    base_url = redirect_url.rsplit("/setup/callback", 1)[0]
+    base_url = redirect_url.rsplit("/setup/callback", 0)[0]
 
     manifest: dict[str, Any] = {
         "name": app_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................F
=================================== FAILURES ===================================
__________________ TestCreateManifest.test_manifest_base_url ___________________

self = <tests.test_manifest.TestCreateManifest object at 0x7b402acad590>

    def test_manifest_base_url(self):
        """Test manifest contains correct base URL derived from redirect URL."""
        manifest = create_manifest(
            redirect_url="https://stampbot.example.com/setup/callback",
        )
    
>       assert manifest["url"] == "https://stampbot.example.com"
E       AssertionError: assert 'https://stam...etup/callback' == 'https://stampbot.example.com'
E         
E         - https://stampbot.example.com
E         + https://stampbot.example.com/setup/callback
E         ?                             +++++++++++++++

tests/test_manifest.py:154: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_base_url - AssertionError: assert 'https://stam...etup/callback' == 'https://stampbot.example.com'
  
  - https://stampbot.example.com
  + https://stampbot.example.com/setup/callback
  ?                             +++++++++++++++
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 272 passed, 3 deselected, 1 warning in 2.24s
operator: core/NumberReplacer, occurrence: 6, definition_name: create_manifest
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -130,7 +130,7 @@
     _validate_url(redirect_url, "redirect_url")
 
     # Extract base URL from redirect URL
-    base_url = redirect_url.rsplit("/setup/callback", 1)[0]
+    base_url = redirect_url.rsplit("/setup/callback", 1)[ 1]
 
     manifest: dict[str, Any] = {
         "name": app_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................F
=================================== FAILURES ===================================
__________________ TestCreateManifest.test_manifest_base_url ___________________

self = <tests.test_manifest.TestCreateManifest object at 0x79610fac5590>

    def test_manifest_base_url(self):
        """Test manifest contains correct base URL derived from redirect URL."""
        manifest = create_manifest(
            redirect_url="https://stampbot.example.com/setup/callback",
        )
    
>       assert manifest["url"] == "https://stampbot.example.com"
E       AssertionError: assert '' == 'https://stampbot.example.com'
E         
E         - https://stampbot.example.com

tests/test_manifest.py:154: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_base_url - AssertionError: assert '' == 'https://stampbot.example.com'
  
  - https://stampbot.example.com
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 272 passed, 3 deselected, 1 warning in 2.19s
operator: core/NumberReplacer, occurrence: 7, definition_name: create_manifest
--- mutation diff ---
--- a/stampbot/manifest.py
+++ b/stampbot/manifest.py
@@ -130,7 +130,7 @@
     _validate_url(redirect_url, "redirect_url")
 
     # Extract base URL from redirect URL
-    base_url = redirect_url.rsplit("/setup/callback", 1)[0]
+    base_url = redirect_url.rsplit("/setup/callback", 1)[ -1]
 
     manifest: dict[str, Any] = {
         "name": app_name,
........................................................................ [ 15%]
........................................................................ [ 30%]
........................................................................ [ 45%]
........................................................F
=================================== FAILURES ===================================
__________________ TestCreateManifest.test_manifest_base_url ___________________

self = <tests.test_manifest.TestCreateManifest object at 0x7143264c9590>

    def test_manifest_base_url(self):
        """Test manifest contains correct base URL derived from redirect URL."""
        manifest = create_manifest(
            redirect_url="https://stampbot.example.com/setup/callback",
        )
    
>       assert manifest["url"] == "https://stampbot.example.com"
E       AssertionError: assert '' == 'https://stampbot.example.com'
E         
E         - https://stampbot.example.com

tests/test_manifest.py:154: AssertionError
=============================== warnings summary ===============================
../../../.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1
  /home/runner/.cache/pypoetry/virtualenvs/stampbot-q2WA0ezo-py3.14/lib/python3.14/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
    from starlette.testclient import TestClient as TestClient  # noqa

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED tests/test_manifest.py::TestCreateManifest::test_manifest_base_url - AssertionError: assert '' == 'https://stampbot.example.com'
  
  - https://stampbot.example.com
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 272 passed, 3 deselected, 1 warning in 2.21s