Skip to content

[Bug] Hermes adapter: _rebuild_if_stale() checks dist/bridge.cjs, but the runtime entry is dist/bridge.mjs — a stale ESM build never triggers a rebuild #2470

Description

@chiefmojo

Summary

_rebuild_if_stale() in apps/memos-local-plugin/adapters/hermes/memos_provider/daemon_manager.py checks whether dist/ is stale against dist/bridge.cjs. Since the ESM migration (#1736 / #1998), both spawn paths prefer dist/bridge.mjs:

  • daemon_manager._bridge_script(): dist/bridge.mjs → dist/bridge.cjs → bridge.mts
  • bridge_client._bridge_script(): same order

So the rebuild guard watches a file that isn't the one that runs.

def _rebuild_if_stale() -> bool:
    """Run `npm run build` if any TypeScript source is newer than dist/bridge.cjs."""
    plugin_root = _plugin_root()
    compiled = plugin_root / "dist" / "bridge.cjs"

Failure mode

If dist/bridge.cjs is newer than the TypeScript sources but dist/bridge.mjs is older (for example, a partial build, or a build step that writes only the CJS entry), the guard returns True and no rebuild happens. The daemon then starts the stale bridge.mjs without any warning. Source changes that touch only bridge.mts, such as #2399 / #2465 (initLogging missing from the ESM entry), are the case where this matters most: the fixed source exists, and the runtime keeps running the old ESM artifact.

Suggested fix

Compare against the same entry the spawner will use, by reusing _bridge_script() precedence, and treat a missing compiled entry as stale:

compiled = next(
    (p for p in (plugin_root / "dist" / "bridge.mjs",
                 plugin_root / "dist" / "bridge.cjs") if p.exists()),
    None,
)

A stricter option is to rebuild when either compiled entry is older than the newest source, since dist/ can hold both.

Test idea

Make dist/bridge.cjs newer than the TypeScript sources and dist/bridge.mjs older than them. Today _rebuild_if_stale() returns True without building; after the fix it should rebuild. The rglob("*.ts") source scan can stay as it is.

Activity

  1. added
    ai:taskDispatched to AI coding agent | 已派发给 AI 编码任务
    status:in-progressSomeone or AI is working on it | 人工或 AI 正在处理
    types:bugSomething isn't working | 功能异常
    on Oct 8, 2026
  2. self-assigned this
    on Oct 8, 2026
  3. Memtensor-AI commented on Oct 8, 2026

    @Memtensor-AI
    Collaborator

    🤖 AutoDev has picked up this issue and started working on it.

    Task ID: beae6a34d8139399
    Working branch: bugfix/autodev-2470-20261008201724571
    Target branch: latest dev* branch
    Workflow: opsp (analysis → coding → testing → PR)

    I will post the PR link here once done. If I need more information, I will ask in the comments.

  4. Memtensor-AI commented on Oct 8, 2026

    @Memtensor-AI
    Collaborator

    ✅ AutoDev task beae6a34d8139399 completed.

    Summary: Fix Issue #2470: _rebuild_if_stale() in the Hermes adapter (apps/memos-local-plugin/adapters/hermes/memos_provider/daemon_manager.py) was comparing TypeScript source timestamps against dist/bridge.cjs, but both spawn paths (daemon_manager._bridge_script() and bridge_client._bridge_script()) prefer dist/bridge.mjs since the ESM migration (#1736 / #1998). This let a stale bridge.mjs next to a fresh bridge.cjs slip past the rebuild guard — the daemon then kept booting the stale ESM artifact without any warning, which is exactly the failure mode #2399 / #2465 triggered.

    The fix mirrors _bridge_script()'s compiled-entry precedence (dist/bridge.mjs, dist/bridge.cjs) and adopts the stricter variant from the issue: rebuild when any compiled entry on disk is older than the newest *.ts source, treat a missing dist/ as stale, and name the stale artifacts in the log line for easier diagnosis of future partial-build regressions.

    Behaviour change is narrow — only two matrix rows flip: (a) the headline bug "stale .mjs + fresh .cjs + newer source" now rebuilds, and (b) "only .mjs present + fresh" now correctly skips the rebuild (old code treated missing .cjs as stale). All other branches (missing npm, build failure, build timeout, both compiled fresh, etc.) keep identical behaviour.

    Tests: new file apps/memos-local-plugin/tests/python/test_rebuild_if_stale.py with 9 unittest cases covers every branch, including the exact failure scenario from the issue (TDD: 2 cases went red before the fix, all 9 green after). Full plugin python suite (141 tests) passes with no regressions; sibling test_bridge_script_resolution (11 tests) also passes. ruff check + ruff format --check both clean. No API / schema / deps change. Reviewers: @whipser030, @hijzy.

    Base branch: main
    Branch: bugfix/autodev-2470-20261008201724571
    Commit: b9b63ff
    PR: #2472
    Assigned to: @syzsunshine219
    Reviewers: @whipser030, @hijzy

  5. added
    ai:testingAI agent is running tests | AI 正在运行测试
    and removed
    ai:codingAI agent is coding | AI 正在编码
    on Oct 8, 2026
  6. added
    ai:failedAI task failed | AI 任务失败
    and removed
    ai:testingAI agent is running tests | AI 正在运行测试
    ai:taskDispatched to AI coding agent | 已派发给 AI 编码任务
    on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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