hermes - 💡(How to fix) Fix MCP `EventBridge` cursor is in-memory — `events_poll` resets to 0 on every subprocess restart

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Root Cause

Practical effect: any MCP client that uses events_poll(after_cursor=N) for incremental event consumption breaks across subprocess restarts. The client's stored next_cursor value becomes meaningless because the new subprocess has no record of which events the client has already seen.

Fix Action

Fix / Workaround

Option B: persist EventBridge cursor to disk. Write self._cursor and self._last_poll_timestamps to ~/.hermes/state/mcp_bridge_cursor.json on every enqueue (debounced). Load on startup. Simpler patch, but each subprocess still owns its own counter — coordination across multiple concurrent subprocesses isn't solved.

Code Example

def __init__(self):
    self._queue: List[QueueEvent] = []
    self._cursor = 0  # in-memory, lost on restart
    ...
RAW_BUFFERClick to expand / collapse

Bug

The EventBridge cursor in mcp_serve.py is process-local in-memory state. Every time an MCP client reconnects (which spawns a new hermes mcp serve subprocess via stdio), the cursor restarts at 0.

Practical effect: any MCP client that uses events_poll(after_cursor=N) for incremental event consumption breaks across subprocess restarts. The client's stored next_cursor value becomes meaningless because the new subprocess has no record of which events the client has already seen.

Reproduce

  1. Start an MCP client connecting to hermes mcp serve. Call events_poll(after_cursor=0) and receive events with cursors 1..N.
  2. Disconnect the client. The subprocess exits.
  3. Reconnect the client. Call events_poll(after_cursor=N). Expect new events since N. Actually receive nothing (the new subprocess's cursor counter starts at 0 and hasn't reached N yet) — or replay of old events as _poll_once rediscovers them.

Source

mcp_serve.py, EventBridge.__init__ (around line 213-225):

def __init__(self):
    self._queue: List[QueueEvent] = []
    self._cursor = 0  # in-memory, lost on restart
    ...

_enqueue increments self._cursor per event but never persists it.

Impact

  • Stdio MCP spawns one subprocess per client connection. Every reconnect = new subprocess = cursor reset.
  • Multiple MCP clients (e.g. multiple Claude Code sessions) each have independent cursor counters that don't agree across processes.
  • Any agent built on events_poll / events_wait for "react to new events since I last looked" patterns breaks across reconnects.

Suggested fix

Option A (preferred): derive cursor from SessionDB. Use the message ID or insertion timestamp from state.db as the cursor. Cursor becomes naturally durable — it is the database's primary key, not bridge-internal state. events_poll(after_cursor=N) becomes "messages with id > N", consistent across subprocess instances.

Option B: persist EventBridge cursor to disk. Write self._cursor and self._last_poll_timestamps to ~/.hermes/state/mcp_bridge_cursor.json on every enqueue (debounced). Load on startup. Simpler patch, but each subprocess still owns its own counter — coordination across multiple concurrent subprocesses isn't solved.

Option A is the cleaner architectural fix.

Affected versions

Confirmed on origin/main at 62c2f5d8d2a6a21adfdea2d8d1f28fd8f04b5dd7. Behavior unchanged at current HEAD 292f46836.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING