Files
hermes-agent/tests/tools/test_mcp_server_reconnecting_predicate.py
teknium1 66a91b7ce8 fix(cron): a server parked on a permanent error blocks the job again; warn once per outage
The reconnecting exemption keyed only on `_ever_connected`, so a server that
connected once and then parked on a PERMANENT error (revoked credentials,
endpoint gone: `_park_and_rearm('from parked state (permanent error)')`) looked
identical to a router-reboot park. Its self-probe fails the same way every
interval, so the job ran tool-less on every tick forever with only a gateway
WARNING and never received the one-shot blocked_config alert it used to get.

Record the revival reason `_park` was given on MCPServerTask (`_park_reason`,
cleared when a session proves healthy) and have `mcp_server_reconnecting`
return False for a permanent-error park, restoring the block and its alert.
Transient parks (network blip, rapid-drop budget exhausted) still return True.

Also dedupe the exemption's WARNING per job+server for the length of one
outage, like the one-shot blocked_config alert, instead of logging every tick;
the entry drops once the server resolves tools again so the next outage warns.
2026-09-17 08:51:30 -07:00

48 lines
1.8 KiB
Python

"""``mcp_server_reconnecting`` (cron preflight's "is this outage healing on its own?" question)
must say True only for a transient park. A server parked on a PERMANENT error (revoked
credentials, endpoint gone) fails its self-probe identically every time, so treating it as
reconnecting would run the job tool-less forever with no blocked_config alert.
"""
import asyncio
import pytest
import tools.mcp_tool as core
from tools.mcp_tool_discovery import mcp_server_reconnecting
@pytest.fixture
def parked_server(monkeypatch):
server = core.MCPServerTask("x")
server._ever_connected = True
server.session = None
server._task = None
monkeypatch.setitem(core._servers, "x", server)
return server
def test_transient_park_is_reconnecting(parked_server):
assert mcp_server_reconnecting("x") is True
parked_server._park_reason = "from parked state" # rapid-drop budget exhausted, still probing
assert mcp_server_reconnecting("x") is True
def test_permanent_error_park_is_not_reconnecting(parked_server):
parked_server._park_reason = "from parked state (permanent error)"
assert mcp_server_reconnecting("x") is False
def test_park_records_reason_and_proven_session_clears_it(parked_server, monkeypatch):
"""The run loop hands ``_park`` its revival reason; a proven healthy session forgets it."""
async def _shutdown_at_once(timeout=None):
return "shutdown"
monkeypatch.setattr(parked_server, "_wait_for_reconnect_or_shutdown", _shutdown_at_once)
asyncio.run(parked_server._park("from parked state (permanent error)"))
assert parked_server._park_reason == "from parked state (permanent error)"
assert mcp_server_reconnecting("x") is False
parked_server._mark_session_proven()
assert parked_server._park_reason is None