From 166d2457b292e10d347331016b440ebbfa2fb66e Mon Sep 17 00:00:00 2001 From: Hao Zhe Date: Wed, 17 Jun 2026 01:32:43 +0800 Subject: [PATCH] fix(memory): avoid setup autostart for unhealthy OpenViking --- plugins/memory/openviking/__init__.py | 28 +++++++++++-- .../memory/test_openviking_provider.py | 40 +++++++++++++++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/plugins/memory/openviking/__init__.py b/plugins/memory/openviking/__init__.py index 452c543d04d..7b626cf7a53 100644 --- a/plugins/memory/openviking/__init__.py +++ b/plugins/memory/openviking/__init__.py @@ -89,6 +89,7 @@ _MEMORY_WRITE_TARGET_SUBDIR_MAP = { _LOCAL_OPENVIKING_HOSTS = {"localhost", "127.0.0.1", "::1"} _LOCAL_OPENVIKING_AUTOSTART_TIMEOUT = 60.0 _OPENVIKING_SERVER_LOG_RELATIVE_PATH = Path("logs") / "openviking-server.log" +_OPENVIKING_RESPONDED_FAILURE_PREFIX = "OpenViking server responded" _SETUP_CANCELLED = object() @@ -806,6 +807,8 @@ def _validate_openviking_reachability(endpoint: str) -> tuple[bool, str]: elif client.health(): return True, "" except Exception as e: + if _status_code_from_error(e) is not None: + return False, f"OpenViking server responded with {_format_openviking_exception(e)}." return False, f"OpenViking server is not reachable at {endpoint}: {_format_openviking_exception(e)}" return False, f"OpenViking server is not reachable at {endpoint}." @@ -984,8 +987,19 @@ def _wait_for_openviking_health(endpoint: str, *, timeout_seconds: float = 15.0) return False -def _handle_unreachable_endpoint(endpoint: str, message: str, select, cancelled): - if _is_local_openviking_url(endpoint): +def _reachability_failure_allows_local_autostart(message: str) -> bool: + return not (message or "").startswith(_OPENVIKING_RESPONDED_FAILURE_PREFIX) + + +def _handle_unreachable_endpoint( + endpoint: str, + message: str, + select, + cancelled, + *, + allow_local_autostart: bool = True, +): + if _is_local_openviking_url(endpoint) and allow_local_autostart: print(f" {message}") choice = select( " Local OpenViking server is down", @@ -1017,7 +1031,7 @@ def _handle_unreachable_endpoint(endpoint: str, message: str, select, cancelled) return _retry_or_cancel_manual_setup( select, - " OpenViking server unreachable", + " OpenViking server unhealthy" if _is_local_openviking_url(endpoint) else " OpenViking server unreachable", message, cancelled, ) @@ -1126,7 +1140,13 @@ def _prompt_manual_connection_values(prompt, select, cancelled, *, service: bool if reachable: print(" OpenViking server is reachable.") break - retry = _handle_unreachable_endpoint(endpoint, message, select, cancelled) + retry = _handle_unreachable_endpoint( + endpoint, + message, + select, + cancelled, + allow_local_autostart=_reachability_failure_allows_local_autostart(message), + ) if retry is True: break if retry is _SETUP_CANCELLED: diff --git a/tests/plugins/memory/test_openviking_provider.py b/tests/plugins/memory/test_openviking_provider.py index d9aba21ca95..518b2e37ded 100644 --- a/tests/plugins/memory/test_openviking_provider.py +++ b/tests/plugins/memory/test_openviking_provider.py @@ -779,6 +779,46 @@ def test_handle_unreachable_endpoint_waits_long_enough_after_autostart(monkeypat assert "Waiting for OpenViking server to become reachable..." in output +def test_manual_setup_does_not_offer_autostart_when_local_server_is_unhealthy(monkeypatch): + _clear_openviking_env(monkeypatch) + + class FakeVikingClient: + def __init__(self, endpoint, api_key="", account="", user="", agent=""): + assert endpoint == "http://localhost:1933" + + def health_payload(self): + return {"healthy": False} + + select_calls = [] + + def select(title, options, **kwargs): + select_calls.append((title, options)) + assert all(label != "Start local OpenViking" for label, _description in options) + return 1 + + monkeypatch.setattr(openviking_module, "_VikingClient", FakeVikingClient) + monkeypatch.setattr( + openviking_module, + "_start_local_openviking_server", + MagicMock(side_effect=AssertionError("unhealthy local server should not offer auto-start")), + ) + + result = openviking_module._prompt_manual_connection_values( + _prompt_from_values({"OpenViking server URL": "localhost"}), + select, + -1, + ) + + assert result is openviking_module._SETUP_CANCELLED + assert select_calls == [( + " OpenViking server unhealthy", + [ + ("Retry", "try this step again"), + ("Cancel setup", "no changes saved"), + ], + )] + + def test_initialize_autostarts_local_openviking_in_background_when_runtime_health_fails(monkeypatch): _clear_openviking_env(monkeypatch) monkeypatch.setenv("OPENVIKING_ENDPOINT", "http://127.0.0.1:1934")