Skip to content

openclaw-tps-mail cannot load under Node: @tpsdev-ai/cli/utils/mail statically imports utils/archive (bun:sqlite) #394

Closed

Description

@tps-flint

Summary

The openclaw-tps-mail plugin built from main (5f6a7cf, after #392) fails to load in the OpenClaw gateway:

[plugins] openclaw-tps-mail failed to load from .../plugins/openclaw-tps-mail/dist/src/index.js:
Error [ERR_UNSUPPORTED_ESM_URL_SCHEME]: Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. Received protocol 'bun:'
[plugins] 1 plugin(s) failed to initialize (load: openclaw-tps-mail)

The gateway then comes up with only the other plugin registered and every agent inbox it was watching goes unprocessed until the previous build is restored.

Mechanism (verified)

  • Since fix(mail): verify envelopes on promotion; make verification non-optional #377 the plugin imports promote, recoverPromoted and sweepStrandedPromoteScratch from @tpsdev-ai/cli/utils/mail.
  • packages/cli/src/utils/mail.ts line 6 statically imports logEvent from ./archive.js.
  • packages/cli/src/utils/archive.ts line 5 imports Database from bun:sqlite.
  • The OpenClaw gateway runs under Node (v26 here), whose ESM loader rejects the bun: scheme at import time, so the whole plugin module graph fails before any code runs.
  • The three functions the plugin uses never call the archiver; the coupling is purely the static import.
  • The previous deployed build (2026-09-02, pre-fix(mail): verify envelopes on promotion; make verification non-optional #377) imports only @tpsdev-ai/agent and @tpsdev-ai/cli/utils/agent-keys and loads fine — that is why this was never seen before.

Why review and CI did not catch it

Every test and both reviews ran under bun test, where bun:sqlite resolves. The plugin is not part of the workspace CI (#393), and nothing loads dist/src/index.js under Node. The production runtime of this package is Node; the test runtime was bun.

Fix shape

  1. Break the static chain: in utils/mail.ts, load the archiver lazily where it is actually used (dynamic import("./archive.js") inside the archiving path), or move logEvent behind a runtime adapter that uses node:sqlite when Bun is undefined (Node ≥ 22.13 ships it). The first is smaller and sufficient for the plugin.
  2. Add a Node-load check to the plugin's tests and to CI: node --input-type=module -e "import('./dist/src/index.js')" from the plugin directory must succeed. This is the one assertion that reproduces the failure exactly.
  3. Optionally, a workspace-level guard that any subpath the plugin imports from @tpsdev-ai/cli is free of bun: imports (a grep over the built dist reachable from those subpaths).

Refs #392, #377, #386, #393.

Activity

  1. tps-flint commented on Sep 22, 2026

    @tps-flint
    ContributorAuthor

    Fixed and verified on a real gateway.

    The CI job now runs both loaders; the remaining hardening items for that job are on #393.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions