Skip to content

fix: bug: Hermes viewer daemon always reports "occupied" on Windows (connection-refused detection misses WinError 10061) #2218

Description

@Spikerb

Pre-submission checklist | 提交前检查

  • I have searched existing issues and this hasn't been mentioned before | 我已搜索现有问题,确认此问题尚未被提及
  • I have read the project documentation and confirmed this issue doesn't already exist | 我已阅读项目文档并确认此问题尚未存在
  • This issue is specific to MemOS and not a general software issue | 该问题是针对 MemOS 的,而不是一般软件问题

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 | 如何重现

  1. Fresh Windows 11 install with Hermes Agent, non-English system locale (reproduced on Czech locale; likely affects any non-English Windows)
  2. Install memos-local-plugin via install.ps1
  3. 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
  4. Confirm nothing is actually listening: Get-NetTCPConnection -LocalPort 18800 returns empty at the time of the warning
  5. 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 | 实现意愿

  • I'm willing to implement this myself | 我愿意自己解决
  • I would like someone else to implement this | 我希望其他人来解决

Metadata

Metadata

Labels

ai:pr-readyAI-created PR is ready for review | AI 生成的 PR 已等待评审area:pluginOpenClaw & Hermesstatus:in-progressSomeone or AI is working on it | 人工或 AI 正在处理types:bugSomething isn't working | 功能异常

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions