Skip to content

[Bug] memos-local-plugin: --daemon entry never starts the status heartbeat — health reports "stale" forever while RPC works #2278

Description

@chiefmojo

Bug report

Summary

Since #1998, the Hermes launcher prefers the pure-ESM dist/bridge.mjs entry over dist/bridge.cjs. However, bridge.mts only starts the bridge-status.json status heartbeat in its stdio branch (if (!args.daemon)). The --daemon branch never calls markConnected() / startHeartbeat() — so no daemon ever refreshes the status file.

The legacy bridge.cts daemon path has both calls right after the viewer port binds; the ESM successor lost them in translation.

Impact

Any --daemon spawn writes no status updates. Readers (health endpoint, external monitors) apply the 20s staleness rule to whatever snapshot is on disk and permanently report:

status: "disconnected"
lastError: "Hermes bridge heartbeat is stale"

…while the bridge is actually healthy: RPC (POST /api/v1/rpc) keeps working normally because it never touches status tracking. Monitoring built on the health endpoint therefore shows a dead bridge during fully functional operation.

Repro

  1. node dist/bridge.mjs --agent=hermes --daemon
  2. Watch <home-root>/bridge-status.json: once stale from a previous run, its mtime/content never changes again after daemon start (no initial write either)
  3. ~20s later, the health endpoint reports bridge.status: disconnected, "Hermes bridge heartbeat is stale" — indefinitely

Observed in production after a v2.0.7 → v2.0.16 jump: the status file froze at the pre-upgrade daemon's last write, and every respawned daemon (fresh PID) left it untouched.

Root cause

apps/memos-local-plugin/bridge.mtsmarkConnected() / startHeartbeat() appear only inside if (!args.daemon). The if (args.daemon) block has none. Compare bridge.cts, which calls both after successful viewer bind in daemon mode.

Suggested fix

Mirror the .cts daemon path — two lines after the bind-retry loop succeeds:

bridgeStatus?.markConnected();
bridgeHeartbeat = bridgeStatus?.startHeartbeat();

PR incoming with the fix plus a source-level regression guard test (same pattern as the existing bridge-startup-ordering.test.ts).

Metadata

Metadata

Labels

ai:failedAI task failed | AI 任务失败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