Pre-submission checklist | 提交前检查
Bug Description | 问题描述
On Windows, the Hermes viewer daemon's port-availability probe
(_probe_json_url in daemon_manager.py) never recognizes an unused port as
"free". It only checks for macOS/Linux ECONNREFUSED errno values (61, 111)
and English-language "connection refused" text, but Windows raises errno
10061 with a message that varies by system locale (e.g. Czech: "cílový
počítač je aktivně odmítl"). As a result, the viewer web panel is
permanently reported as blocked by a "non-MemOS service" even when nothing
is listening on the port, and http://127.0.0.1:18800/ never becomes usable
on a fresh Windows install.
How to Reproduce | 如何重现
- Fresh Windows 11 install with Hermes Agent, non-English system locale (reproduced on Czech locale; likely affects any non-English Windows)
- Install memos-local-plugin via install.ps1
- Start Hermes and check agent.log — observe:
WARNING daemon_manager: MemOS: viewer port 18800 is occupied by a non-MemOS service; memory capture will continue without the web panel
- Confirm nothing is actually listening: Get-NetTCPConnection -LocalPort 18800 returns empty at the time of the warning
- Memory Viewer at http://127.0.0.1:18800/ never becomes reachable
Environment | 环境信息
- Python version: 3.11.15
- Operating System: Windows 11 (build 26100), Czech system locale
- MemOS version: (run
pip show memoryos inside the plugin's venv, or check package.json version in memos-local-plugin folder)
Additional Context | 其他信息
Root cause: _probe_json_url() in daemon_manager.py only recognizes macOS/Linux ECONNREFUSED errno values (61, 111) and English "connection refused" text as "port is free". Windows raises errno 10061 (WSAECONNREFUSED) with a locale-dependent message (e.g. Czech: "cílový počítač je aktivně odmítl"), which matches neither check, so an unused port is always misclassified as "blocked".
Suggested fix in _probe_json_url():
except urllib.error.URLError as err:
reason = getattr(err, "reason", None)
errno = getattr(reason, "errno", None)
# 61 = macOS ECONNREFUSED, 111 = Linux ECONNREFUSED, 10061 = Windows WSAECONNREFUSED
if errno in {61, 111, 10061}:
return "free"
if isinstance(reason, ConnectionRefusedError):
return "free"
msg = str(err).lower()
if "connection refused" in msg or "failed to establish" in msg:
return "free"
return "blocked"
Using isinstance(reason, ConnectionRefusedError) is the most robust check since Python raises this exception type consistently across platforms, regardless of OS errno or locale-specific message text.
Impact: memory capture (stdio bridge) still works, but the web viewer panel is silently and permanently disabled on Windows installs, with no clear indication that this is a platform-detection bug rather than an actual port conflict.
Willingness to Implement | 实现意愿
Pre-submission checklist | 提交前检查
Bug Description | 问题描述
On Windows, the Hermes viewer daemon's port-availability probe
(_probe_json_url in daemon_manager.py) never recognizes an unused port as
"free". It only checks for macOS/Linux ECONNREFUSED errno values (61, 111)
and English-language "connection refused" text, but Windows raises errno
10061 with a message that varies by system locale (e.g. Czech: "cílový
počítač je aktivně odmítl"). As a result, the viewer web panel is
permanently reported as blocked by a "non-MemOS service" even when nothing
is listening on the port, and http://127.0.0.1:18800/ never becomes usable
on a fresh Windows install.
How to Reproduce | 如何重现
WARNING daemon_manager: MemOS: viewer port 18800 is occupied by a non-MemOS service; memory capture will continue without the web panel
Environment | 环境信息
pip show memoryosinside the plugin's venv, or check package.json version in memos-local-plugin folder)Additional Context | 其他信息
Root cause:
_probe_json_url()in daemon_manager.py only recognizes macOS/Linux ECONNREFUSED errno values (61, 111) and English "connection refused" text as "port is free". Windows raises errno 10061 (WSAECONNREFUSED) with a locale-dependent message (e.g. Czech: "cílový počítač je aktivně odmítl"), which matches neither check, so an unused port is always misclassified as "blocked".Suggested fix in
_probe_json_url():Using
isinstance(reason, ConnectionRefusedError)is the most robust check since Python raises this exception type consistently across platforms, regardless of OS errno or locale-specific message text.Impact: memory capture (stdio bridge) still works, but the web viewer panel is silently and permanently disabled on Windows installs, with no clear indication that this is a platform-detection bug rather than an actual port conflict.
Willingness to Implement | 实现意愿