From ab7e5e3af485d80ef60b28dde049e35a9f478076 Mon Sep 17 00:00:00 2001 From: "Tessa (livepeer-tessa)" Date: Wed, 15 Apr 2026 06:16:56 +0000 Subject: [PATCH] fix: downgrade bundled plugin uninstall from ERROR to WARNING MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attempting to uninstall a bundled plugin is an expected, handled condition — not a server-side failure. This was generating ERROR-level log noise on every prod fal.ai job that triggered the cleanup path (issue #837). Changes: - Add PluginBundledError(PluginInstallError) subclass in manager.py so callers can distinguish bundled-rejection from true install failures - Raise PluginBundledError instead of PluginInstallError in _uninstall_plugin_sync when a bundled plugin is targeted - In app.py uninstall endpoint, catch PluginBundledError before the generic PluginInstallError handler: log at WARNING, return HTTP 400 (client error) instead of 500 (server error) Fixes: #837 Signed-off-by: Tessa (livepeer-tessa) --- src/scope/core/plugins/__init__.py | 2 ++ src/scope/core/plugins/manager.py | 8 +++++++- src/scope/server/app.py | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/scope/core/plugins/__init__.py b/src/scope/core/plugins/__init__.py index a681707d7..fcf208413 100644 --- a/src/scope/core/plugins/__init__.py +++ b/src/scope/core/plugins/__init__.py @@ -3,6 +3,7 @@ from .hookspecs import hookimpl from .manager import ( FailedPluginInfo, + PluginBundledError, PluginDependencyError, PluginInstallError, PluginInUseError, @@ -32,4 +33,5 @@ "PluginNameCollisionError", "PluginDependencyError", "PluginInstallError", + "PluginBundledError", ] diff --git a/src/scope/core/plugins/manager.py b/src/scope/core/plugins/manager.py index 87ee5a48a..399bea71e 100644 --- a/src/scope/core/plugins/manager.py +++ b/src/scope/core/plugins/manager.py @@ -80,6 +80,12 @@ class PluginInstallError(Exception): pass +class PluginBundledError(PluginInstallError): + """Attempted to uninstall a bundled (built-in) plugin.""" + + pass + + @dataclass(frozen=True) class FailedPluginInfo: """Information about a plugin entry point that failed to load.""" @@ -1360,7 +1366,7 @@ def _uninstall_plugin_sync( # Prevent uninstalling bundled plugins if plugin_info.get("bundled"): - raise PluginInstallError( + raise PluginBundledError( f"Plugin '{name}' is bundled and cannot be uninstalled" ) diff --git a/src/scope/server/app.py b/src/scope/server/app.py index 470771551..564a392a1 100644 --- a/src/scope/server/app.py +++ b/src/scope/server/app.py @@ -2957,6 +2957,7 @@ async def uninstall_plugin( cloud-hosted scope backend. """ from scope.core.plugins import ( + PluginBundledError, PluginInstallError, PluginNotFoundError, get_plugin_manager, @@ -2991,6 +2992,12 @@ async def uninstall_plugin( status_code=404, detail=f"Plugin '{name}' not found", ) from e + except PluginBundledError as e: + logger.warning(f"Plugin uninstall rejected (bundled plugin): {name} - {e}") + raise HTTPException( + status_code=400, + detail=str(e), + ) from e except PluginInstallError as e: logger.error(f"Plugin uninstall failed: {name} - {e}") raise HTTPException(