Skip to content

Fix #2278: [Bug] memos-local-plugin: --daemon entry never starts the status heartbeat — hea - #2280

Closed
Memtensor-AI wants to merge 1 commit into
MemTensor:dev-v2.0.30from
Memtensor-AI:bugfix/autodev-2278-20260823133455125
Closed

Fix #2278: [Bug] memos-local-plugin: --daemon entry never starts the status heartbeat — hea#2280
Memtensor-AI wants to merge 1 commit into
MemTensor:dev-v2.0.30from
Memtensor-AI:bugfix/autodev-2278-20260823133455125

Conversation

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

Description

Fixes #2278: the memos-local-plugin --daemon bridge entry never started the bridge-status heartbeat, so a daemon-spawned bridge never refreshed bridge-status.json and the health endpoint permanently reported "Hermes bridge heartbeat is stale" while RPC kept working.

Root cause: the pure-ESM entry apps/memos-local-plugin/bridge.mts (built to dist/bridge.mjs, which the Python launcher has preferred since #1998) only called markConnected() / startHeartbeat() inside its stdio branch (if (!args.daemon)); the --daemon branch had no status calls at all. A source-level git-history check confirmed the legacy bridge.cts daemon path had the same gap (never called there even in the original signal-light commit 22ccacb), which is why the ESM translation lost it.

Fix: mirror the stdio pattern into the daemon branch of both entries — after the viewer bind-retry loop succeeds, call bridgeStatus?.markConnected() and bridgeHeartbeat = bridgeStatus?.startHeartbeat(); stop the heartbeat in shutdownDaemon (SIGINT/SIGTERM). The bridge core is fully initialized before the daemon block, so "connected" is never claimed prematurely; OpenClaw is unaffected (all calls are ?.-guarded no-ops since bridgeStatus is null for openclaw); the heartbeat timer is unref()ed and does not hold the daemon open. Stdio-mode behavior is unchanged and pinned by the new regression test.

Regression guard: added apps/memos-local-plugin/tests/unit/bridge/bridge-daemon-heartbeat.test.ts, a source-level guard following the existing bridge-startup-ordering.test.ts pattern (#1747). It asserts both entries start the heartbeat inside the if (args.daemon) branch while the stdio branch keeps its own calls. Verified TDD-style: the test fails on the pre-fix code (both entries) and passes after the fix.

Test evidence (real output): vitest run tests/unit/bridge tests/unit/bridge-status.test.ts -> 8 files / 39 tests passed; tsc -p tsconfig.json --noEmit -> exit 0; vitest run tests/unit -> 164 files / 1351 passed, 1 skipped. (An initial full-suite run showed failures in unrelated modules caused by pnpm skipping native build scripts locally — better-sqlite3/esbuild/onnxruntime/sharp; after approving those builds the entire suite is green. The change touches only bridge.mts, bridge.cts, and the new test.)

opsp artifacts archived to the memos-autodev-specs repo (task.md + proposal/spec/design/tasks/test-cases/verification-report) on commit b89a59e.

Related Issue (Required): Fixes #2278

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (does not change functionality, e.g. code style improvements, linting)
  • Documentation update

How Has This Been Tested?

Automated tests are pending.

  • Unit Test
  • Test Script Or Test Steps (please provide)
  • Pipeline Automated API Test (please provide)

Checklist

  • I have performed a self-review of my own code
  • I have commented my code in hard-to-understand areas
  • I have added tests that prove my fix is effective or that my feature works
  • I have created related documentation issue/PR in MemOS-Docs (if applicable)
  • I have linked the issue to this PR (if applicable)
  • I have mentioned the person who will review this PR

@whipser030, @hijzy please review this PR.

Reviewer Checklist

The pure-ESM bridge entry (bridge.mts -> dist/bridge.mjs, preferred by the
launcher since MemTensor#1998) only started the bridge-status.json heartbeat in its
stdio branch; the --daemon branch never called markConnected() /
startHeartbeat(), so daemon spawns never refreshed the status file and the
health endpoint reported "Hermes bridge heartbeat is stale" forever while
RPC kept working. Mirror the stdio pattern into the daemon branch after the
viewer binds (and stop the heartbeat on shutdown). Apply the same parity fix
to legacy bridge.cts, whose daemon path had the identical gap. Add a
source-level regression guard (pattern of bridge-startup-ordering.test.ts).

Fixes MemTensor#2278
@Memtensor-AI Memtensor-AI added ai:generated Generated or modified by AI | 由 AI 生成或修改 area:plugin OpenClaw & Hermes status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Aug 23, 2026
@Memtensor-AI

Copy link
Copy Markdown
Collaborator Author

🤖 Open Code Review

Target: PR #2280
Task: 143f598f8eb246dd
Base: dev-v2.0.30
Head: bugfix/autodev-2278-20260823133455125
Head SHA: 223fc8f447c5a8bacb70f0baf9ec4346a10cf8c6

OpenCodeReview: Review skipped: no items were selected.

Generated by cloud-assistant via Open Code Review.

@Memtensor-AI

Copy link
Copy Markdown
Collaborator Author

⚠️ Automated Test Results: ENV ISSUE

The test environment encountered an issue that requires manual attention.

Details: Executor error: Command failed: git clone --depth 1 --branch bugfix/autodev-2278-20260823133455125 git@github.com:Memtensor-AI/MemOS.git /data/test-workspaces/143f598f8eb246dd/repo
Cloning into '/data/test-workspaces/143f598f8eb246dd/repo'...
kex_exchange_identification: Connection closed by remote host
Connection closed by UNKNOWN port 65535
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
Branch: bugfix/autodev-2278-20260823133455125

@chiefmojo

Copy link
Copy Markdown
Contributor

We hit this exact bug in production after upgrading a Hermes deployment from v2.0.7 to v2.0.16 — the launcher switched from dist/bridge.cjs to dist/bridge.mjs underneath us:

  • bridge-status.json froze at the pre-upgrade daemon's last write; every respawned daemon (fresh PID) never touched it again
  • the health endpoint reported disconnected / "Hermes bridge heartbeat is stale" continuously, while loopback RPC (memory.search) returned results + injected context the whole time — status tracking just isn't exercised by the RPC path

Deploying the two-line fix (daemon branch calls markConnected() and starts the periodic heartbeat) restored everything: the status file now advances at the exact 5s heartbeat interval and health reports connected with lastOk age of ~2s.

Also reviewed this PR against our equivalent patch (#2279, now closed in favor of this one). This is the better fix: it repairs both entries — including the .cts daemon gap present on main — and tears the heartbeat down in the shutdown handlers. Ran the new regression guard ourselves: RED on pristine main (both entries fail), GREEN on this branch. Confirmed working in production.

@Hun-ger

Hun-ger commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Thanks for investigating #2278 and for adding regression coverage.

We reproduced the reported stale-heartbeat behavior and replaced this approach on fix-local-plugin-260824 with commit 068a701a. The Viewer daemon is an HTTP process, not the owner of the Hermes Python-provider ↔ Node stdio connection. Having daemon mode call markConnected() and refresh the heartbeat can therefore report Hermes as connected even when hermes chat is not running.

The replacement makes the stdio bridge the sole status writer and keeps daemon mode read-only. It reconciles expired status with the actual Hermes Chat process state without rewriting bridge-status.json. The packaged installation was verified end-to-end on both macOS and Windows, including the ESM dist/bridge.mjs entry, actual listener PID, repeated health reads, and unchanged status-file hash/mtime.

Closing this PR as superseded by the safer ownership model in 068a701a. #2278 should be closed when the aggregate branch lands in main.

@Hun-ger Hun-ger closed this Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:generated Generated or modified by AI | 由 AI 生成或修改 area:plugin OpenClaw & Hermes status:in-progress Someone or AI is working on it | 人工或 AI 正在处理

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants