Skip to content

fix(memos-local-plugin): close all pipe fds in bridge_client to prevent process leaks#1676

Open
rayraiser wants to merge 2 commits intoMemTensor:mainfrom
rayraiser:fix/bridge-stdio-pipe-leak
Open

fix(memos-local-plugin): close all pipe fds in bridge_client to prevent process leaks#1676
rayraiser wants to merge 2 commits intoMemTensor:mainfrom
rayraiser:fix/bridge-stdio-pipe-leak

Conversation

@rayraiser
Copy link
Copy Markdown

Problem

The close() method in bridge_client.py only closed stdin, leaving stdout and stderr pipe file descriptors open. Because the bridge process (node tsx) only exits when stdin reaches EOF and there are no open pipe references, this caused bridge processes to accumulate as orphans (e.g., 78+ processes per session).

Root Cause

  • close() called self._proc.stdin.close() but never closed stdout/stderr
  • The Node bridge process stayed alive, holding the pipe open
  • New sessions spawned new bridge processes, each leaking

Fix

Close all pipe fds (stdin, stdout, stderr) in close(), then wait() up to 2s for graceful exit before terminate().

Impact

  • Bridge processes now exit cleanly on session end
  • No orphan process accumulation
  • Viewer will restart on new session (acceptable trade-off)

Files Changed

  • apps/memos-local-plugin/adapters/hermes/memos_provider/bridge_client.py

@rayraiser rayraiser changed the title fix(hermes): close all pipe fds in bridge_client to prevent process leaks fix(memos-local-plugin): close all pipe fds in bridge_client to prevent process leaks May 10, 2026
…nagement

Fix three interrelated issues causing bridge process leaks:

1. bridge_client.py: Restore original close() behavior - only close stdin,
   don't terminate the daemon. This allows the bridge process to survive
   as a daemon across client reconnects.

2. __init__.py: Add daemon detection before spawn. Check if port 18800
   is already bound and healthy before spawning a new bridge. Reuse
   existing daemon instead of blind spawn -> close -> spawn loop.

3. daemon_manager.py: Add bridge lifecycle guard with:
   - _is_port_bound() / _bridge_health_check() for daemon detection
   - Process age detection (10s grace period) to avoid killing
     initializing bridges
   - _cleanup_zombie_bridges() to kill orphaned processes
   - Port-owner tracking to keep only the process that actually binds 18800

Result: Bridge process count stabilizes at 2 (parent tsx + child node)
instead of growing to 6+ with continuous zombie spawn cycles.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant