Skip to content

Gateway calls plugin register() ~30x per boot for non-bundled plugins (CostClaw thrash) #69250

@markthebest12

Description

@markthebest12

Summary

On a single gateway process boot, the plugin loader invokes a non-bundled plugin's register(api) default export approximately 30 times in rapid succession (sub-second spacing), despite the loader having a seenIds Map dedup. Combined with slow plugin init paths (e.g. better-sqlite3 native binding + disk I/O), this saturates the event loop and blocks health endpoints long enough for external watchdogs to kill and restart the gateway — creating a cascade.

Observed on 2026-04-19 (openclaw-gateway-unstable-44e5b62c, Node 22.22.2, macOS arm64)

  • 3272 CostClaw initialized — db: ... events in 10 minutes on a single host
  • 103 gateway boots in one 16-hour window (counted via "loaded 4 internal hook handlers" log line)
  • 3272 / 103 ≈ ~32 register() calls per boot
  • Per-burst timing: 42 inits within 8 seconds (sub-second intervals)

The plugin's register() function (single export default function register(api)) logs exactly once per call, so 42 inits = 42 invocations.

Expected

Each plugin's register() should be called once per gateway boot (or once per explicit reload event). The seenIds Map should prevent duplicates.

Actual

register() called ~30x per boot. Gateway log also emits [plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load: costclaw, imessage-housekeeping on every boot even when plugins.allow in the config contains an explicit list like ['imessage-housekeeping', 'bluebubbles', 'slack', 'telegram', 'ollama', 'amazon-bedrock', 'memory-core'] — suggesting the allow list is being read as empty after bundled-ID filtering (possibly a separate but related bug).

Suspected location

  • Plugin loader: dist/loader-DtbRRnBD.js (approximate path in the bundled build)
  • Log call site: dist/subsystem-DX_oTata.js:328:14 (the subsystem logger)
  • Per the minified source, loadOpenClawPlugins()for (const candidate of orderedCandidates)createApi()register(api). Deduplication via seenIds Map is present but appears to be bypassed for some discovery code path. Possible candidates:
    • discoverPlugins() returning duplicates when auto-discovery overlaps with explicit plugins.entries
    • Channel or hook registration iterating the plugin list separately
    • A retry/reconciliation loop after initial load failure

Reproduction

  1. Install any non-bundled plugin (the canonical case is costclaw-telemetry@0.1.0 with its register() doing initDb + loadRules + api.logger.info('CostClaw initialized — db: ...')).
  2. Start the gateway.
  3. tail the JSON log and count occurrences of the plugin's register-time log line. If non-1, this bug applies.

Environment

  • Gateway: openclaw-gateway-unstable-44e5b62c (from pinned nix-openclaw at rev a003810ddd72d236eefac2ae784c948995974455)
  • Runtime: Node 22.22.2 on macOS arm64
  • Observed on: 2026-04-19, two separate agents (gaston and colette) both reproduce it

Downstream impact

For us (markthebest12/openclaw-infra) this caused iMessage dispatch to stop responding for hours while the gateway was stuck in a watchdog restart cascade. We mitigated by physically moving the plugin directory out of the auto-discovery scan root (markthebest12/openclaw-infra#944, #946, #947) — so our side is unblocked, but the register-multiplier bug remains latent and would reappear for any plugin that does non-trivial I/O in its register().

Asks

  1. Identify why register() fires N times for non-bundled plugins on a single boot.
  2. Confirm that seenIds dedup is on the right axis (plugin ID) and not being bypassed by a different iteration root.
  3. Secondary: clarify the semantics of plugins.allow when the list contains bundled IDs — the "plugins.allow is empty" warning appears to be triggered by bundled-ID filtering rather than a literally empty list.

Happy to provide additional log captures, minified code slices, or canon-side diagnostics if useful. References in our repo: markthebest12/openclaw-infra#943.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions