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
- 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: ...')).
- Start the gateway.
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
- Identify why
register() fires N times for non-bundled plugins on a single boot.
- Confirm that
seenIds dedup is on the right axis (plugin ID) and not being bypassed by a different iteration root.
- 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.
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 aseenIdsMap dedup. Combined with slow plugin init paths (e.g.better-sqlite3native 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)
CostClaw initialized — db: ...events in 10 minutes on a single host"loaded 4 internal hook handlers"log line)The plugin's
register()function (singleexport 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). TheseenIdsMap 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-housekeepingon every boot even whenplugins.allowin 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
dist/loader-DtbRRnBD.js(approximate path in the bundled build)dist/subsystem-DX_oTata.js:328:14(the subsystem logger)loadOpenClawPlugins()→for (const candidate of orderedCandidates)→createApi()→register(api). Deduplication viaseenIdsMap is present but appears to be bypassed for some discovery code path. Possible candidates:discoverPlugins()returning duplicates when auto-discovery overlaps with explicitplugins.entriesReproduction
costclaw-telemetry@0.1.0with itsregister()doinginitDb+loadRules+api.logger.info('CostClaw initialized — db: ...')).tailthe JSON log and count occurrences of the plugin's register-time log line. If non-1, this bug applies.Environment
openclaw-gateway-unstable-44e5b62c(from pinnednix-openclawat reva003810ddd72d236eefac2ae784c948995974455)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 itsregister().Asks
register()fires N times for non-bundled plugins on a single boot.seenIdsdedup is on the right axis (plugin ID) and not being bypassed by a different iteration root.plugins.allowwhen 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.