Skip to content

fix: initialize() spawns new headless bridge without closing old one — process leak #1927

@yujunos

Description

@yujunos

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 | 问题描述

Plugin Version

  • @memtensor/memos-local-plugin v2.0.7
  • Platform: Hermes Agent (Linux, 2-core 4GB)

Description

When the Hermes memory provider's initialize() is called (e.g. on each session start or reconnect), a new MemosBridgeClient (and thus a new --no-viewer Node.js bridge process) is spawned without closing the previous one. The old bridge process becomes an orphan, consuming ~93 MB of RAM each. Over time, these accumulate indefinitely.

The headless bridge reaper (_reap_stale_headless_bridges_locked) does NOT clean these up because their parent process is still alive — it only reaps bridges whose parent has died or are legacy entries older than HEADLESS_STALE_SECONDS.

Root Cause

File: plugins/memory/memtensor/__init__.py, initialize() (line ~296)

def initialize(self, session_id, **kwargs):
    # ...
    ensure_bridge_running()
    new_bridge = MemosBridgeClient()   # ← spawns new Node process
    self._bridge = new_bridge          # ← overwrites old reference, old bridge leaked

Compare with _reconnect_bridge() (line ~1727) which does it correctly:

def _reconnect_bridge(self, ...):
    old_bridge = self._bridge
    if old_bridge:
        old_bridge.close()             # ← correctly closes old bridge first
    new_bridge = MemosBridgeClient()

Additionally: In bridge_client.py _reap_stale_headless_bridges_locked() (line ~206), the reaper only kills bridges when parent_dead or legacy_stale. Since the parent (Hermes dashboard or gateway) remains alive, none of the leaked bridges are ever reaped.

And: bridge.cjs (line ~57) intentionally skips the PID-file singleton guard for --no-viewer bridges, so there's no Node-side protection either.

How to Reproduce | 如何重现

  1. Run Hermes with memory.provider: memtensor configured
  2. Start a hermes dashboard or hermes gateway session
  3. Each session start / reconnect calls initialize() and spawns a new bridge
  4. Check ps aux | grep bridge.cjs — orphaned --no-viewer processes accumulate

Observed timeline from a live instance (dashboard PID 333413):

23:40:53  PID 335421  --no-viewer  (initialize call #1)
23:40:57  PID 335577  --no-viewer  (initialize call #2, 4s later)
23:42:15  PID 337303  --no-viewer  (initialize call #3)
23:42:17  PID 337443  --no-viewer  (initialize call #4, 2s later)
23:47:42  PID 342591  --no-viewer  (initialize call #5)
23:47:45  PID 342675  --no-viewer  (initialize call #6, 3s later)

6 orphaned bridges = ~558 MB wasted RAM on a 3.6 GB machine.

Suggested Fix

In initialize(), close the existing bridge before creating a new one:

def initialize(self, session_id, **kwargs):
    # Close previous bridge if any
    if self._bridge:
        old_pid = getattr(self._bridge, "pid", "?")
        logger.info("MemOS: closing previous bridge (pid=%s) before re-init", old_pid)
        with contextlib.suppress(Exception):
            self._bridge.close()
        self._bridge = None

    # ... rest of existing initialize() code ...

Optionally, also strengthen the reaper in bridge_client.py to deduplicate bridges under the same living parent (keep only the most recent one per parent).

Environment | 环境信息

  • OS: Ubuntu 24.04.4 LTS (2-core, 4GB RAM)
  • Node.js: v22.x
  • Hermes Agent: latest (Telegram gateway + CLI dashboard)
  • memos-local-plugin: v2.0.7 (installed via npm)

Additional Context | 其他信息

{
"bridges": [
{"pid": 335421, "parentPid": 333413, "startedAt": 1781538054.3, "agent": "hermes"},
{"pid": 335577, "parentPid": 333413, "startedAt": 1781538058.4, "agent": "hermes"},
{"pid": 337303, "parentPid": 333413, "startedAt": 1781538136.1, "agent": "hermes"},
{"pid": 337443, "parentPid": 333413, "startedAt": 1781538138.2, "agent": "hermes"},
{"pid": 342591, "parentPid": 333413, "startedAt": 1781538463.7, "agent": "hermes"},
{"pid": 342675, "parentPid": 333413, "startedAt": 1781538466.0, "agent": "hermes"},
{"pid": 359586, "parentPid": 355477, "startedAt": 1781539434.6, "agent": "hermes"}
]
}

Willingness to Implement | 实现意愿

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

Metadata

Metadata

Assignees

Labels

ai-pr-readyAutoDev tests passed and PR is ready for human review/merge.bugSomething isn't working | 功能异常good first issueGood for newcomers | 适合新手help wantedExtra attention is needed | 需要社区帮助pluginPlugin/adapter/bridge layer (apps/ directory) | 插件/适配层

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions