diff --git a/headroom/cli/proxy.py b/headroom/cli/proxy.py index 1ab492acc..1bb88adaf 100644 --- a/headroom/cli/proxy.py +++ b/headroom/cli/proxy.py @@ -1273,9 +1273,13 @@ Press Ctrl+C to stop. import asyncio as _asyncio - from headroom.memory.adapters.watchdog import EmbeddingServerWatchdog - async def _start_embed_watchdog() -> Any: + # Import lazily inside the guarded coroutine. The sidecar module is + # optional and may be absent; keeping the import here lets the + # try/except below fall back to the per-worker embedder instead of + # crashing the proxy at startup with ModuleNotFoundError. + from headroom.memory.adapters.watchdog import EmbeddingServerWatchdog + wd = EmbeddingServerWatchdog(socket_path=_embed_socket) await wd.start() ok = await wd.wait_until_healthy(timeout=30.0) diff --git a/tests/test_cli_proxy_embedding_server.py b/tests/test_cli_proxy_embedding_server.py new file mode 100644 index 000000000..df849a850 --- /dev/null +++ b/tests/test_cli_proxy_embedding_server.py @@ -0,0 +1,31 @@ +"""Regression test for `proxy --embedding-server` startup fallback. + +The optional embedding-server sidecar module +(`headroom.memory.adapters.watchdog`) is not present on main, yet the +`--embedding-server` flag advertises a graceful fallback to the per-worker +embedder. A misplaced import made the flag raise ``ModuleNotFoundError`` at +startup and crash the proxy instead of falling back. +""" + +import sys + +from click.testing import CliRunner + +from headroom.cli import main + + +def test_embedding_server_missing_sidecar_falls_back(monkeypatch): + # Make the optional sidecar module unimportable regardless of whether it is + # installed, so the fallback path is exercised deterministically. + monkeypatch.setitem(sys.modules, "headroom.memory.adapters.watchdog", None) + + # Don't actually start a server. + import headroom.proxy.server as server_mod + + monkeypatch.setattr(server_mod, "run_server", lambda *args, **kwargs: None) + + result = CliRunner().invoke(main, ["proxy", "--embedding-server", "--port", "8799"]) + + assert result.exit_code == 0, f"proxy crashed instead of falling back: {result.output}" + assert result.exception is None + assert "Falling back to per-worker embedder" in result.output