openclaw - ✅(Solved) Fix [Bug]: Plugin loader silently drops register* on missing required fields — should hard-fail or throw [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#72459Fetched 2026-04-27 05:30:02
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×2

The plugin loader silently drops register* calls that fail acceptance gates. The plugin still loads, the surface never fires, and the only signal is a level=warn diagnostic in gateway.log. Plugins ship green tests against helper-mock APIs and broken in production.

Error Message

The plugin loader silently drops register* calls that fail acceptance gates. The plugin still loads, the surface never fires, and the only signal is a level=warn diagnostic in gateway.log. Plugins ship green tests against helper-mock APIs and broken in production.

  1. Hard-fail the plugin loadStatus: error with the diagnostic surfaced via openclaw plugins doctor, or
  2. Throw synchronously from the api.registerX(...) call so the plugin's own error path runs (so unit tests can catch it via expect(...).toThrow) Either is fine; the current "silent drop, log warn, plugin still loads" is the worst of both worlds — the plugin author has no defect signal until production. Helper-mock unit tests against a fake api cannot catch these gates because the mock has no gate logic. Plugin loads successfully. Surface never wires up. The only signal is a level=warn diagnostic in gateway.log that nothing in CI inspects. In loader-*.js registerHook and the gated memory-* registrations, replace pushDiagnostic({ level: "warn"|"error", ... }) + early return with one of:

Root Cause

Two real bugs hit our plugin (1fanwang/openclaw-lifework):

  • PR #38 registered a before_tool_call/after_tool_call telemetry hook without opts.name. Helper test passed. Gateway dropped the hook. Telemetry was disabled in production for weeks before we noticed (it was caught while debugging an unrelated dashboard issue).
  • PR #40 registered registerMemoryCapability(...) without declaring kind: "memory" in the manifest. Helper test passed. Gateway rejected the registration at every load. Memory artifacts were never indexed.

Both fixes are trivial once you know about them. The cost is the time-to-detect — which is open-ended without a hard-fail surface.

Fix Action

Fix / Workaround

Workaround we're shipping today

PR fix notes

PR #72494: fix(plugins): fail loud on rejected registration gates

Description (problem / solution / changelog)

Summary

  • Make plugin registration fail loudly by throwing when api.registerHook(...) is called without opts.name, and when non-memory plugins call api.registerMemoryCapability(...).
  • Add loader regression tests that assert these invalid registrations now produce status: error, preserve expected side effects (no hook/memory capability registration), and emit error diagnostics.
  • Add an Unreleased changelog entry for issue #72459.

Root cause

The plugin API gate checks for these invalid registrations were emitting diagnostics and returning early, but not throwing. Because plugin register(...) did not fail, the loader kept plugin status as loaded even though the requested surface was silently dropped.

Why this fix is safe

The change is narrowly scoped to two existing validation gates and preserves behavior for all valid plugin registrations. It only changes invalid input handling from silent-drop to explicit failure, which matches existing loader error handling paths and diagnostics.

Security/runtime controls unchanged

  • No trust model changes.
  • No permission or policy bypass changes.
  • No prompt-text-based enforcement added.
  • Existing runtime-enforced registration gates remain in place; this change only upgrades failure signaling from silent rejection to hard failure.

Test plan

  • pnpm test src/plugins/loader.test.ts -- --reporter=verbose --testNamePattern="fails plugin load when registerHook is missing opts.name|fails plugin load when non-memory plugin registers a memory capability"
  • pnpm test src/plugins/registry.dual-kind-memory-gate.test.ts src/plugins/loader.test.ts -- --reporter=verbose --testNamePattern="dual-kind memory registration gate|fails plugin load when registerHook is missing opts.name|fails plugin load when non-memory plugin registers a memory capability"
  • git diff --check
  • pnpm check:changed

AI review

  • Attempted pre-PR codex review --base origin/main in a separate temporary worktree, but review execution could not complete in this environment due authentication failure.

Made with Cursor

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • src/plugins/loader.test.ts (modified, +114/-0)
  • src/plugins/registry.ts (modified, +5/-35)

PR #72577: [codex] Fail invalid plugin registration gates loudly

Description (problem / solution / changelog)

Summary

Fixes #72459.

This makes loader-owned plugin registration acceptance gates fail loudly during register(api) instead of logging and silently dropping invalid registrations. Missing api.registerHook(...) names and memory-exclusive registrations from non-memory plugins now throw synchronously, which reuses the existing loader rollback path and marks the plugin as Status: error with a register failure phase.

Root cause

The real plugin loader validated some registration gates by pushing diagnostics and returning early from api.registerX(...). That allowed the plugin itself to finish loading as loaded, even though the registered surface never became active. Helper-mock tests could not catch the production-only gate behavior.

Changes

  • Throw from the real registration API for missing hook names.
  • Throw from memory-exclusive registration APIs when the plugin does not declare/resolve kind: "memory".
  • Keep dual-kind memory slot policy skips as warnings.
  • Add loader-backed regression tests for the hook and memory-capability failure cases.
  • Document that plugin authors need loader-backed smoke tests for registration contracts.
  • Add a changelog entry crediting the issue reporter.

Validation

  • pnpm test src/plugins/loader.test.ts src/plugins/registry.dual-kind-memory-gate.test.ts -- --reporter=verbose -t "fails plugin registration when|dual-kind memory registration gate"
  • pnpm test src/plugins/loader.test.ts src/plugins/registry.dual-kind-memory-gate.test.ts -- --reporter=verbose
  • pnpm check:changed
  • pnpm build
  • Local end-to-end gateway loop with an isolated bad plugin: gateway run reached ready, logged bad-hook failed during register, and plugins doctor surfaced bad-hook [register]: Error: hook registration missing name.

Note: pnpm test:changed ran through the changed-test shards but failed after the final vitest.e2e.config.ts shard produced no output twice and was killed by the wrapper; prior shards, including the plugin loader coverage, passed.

Changed files

  • CHANGELOG.md (modified, +1/-0)
  • docs/plugins/sdk-testing.md (modified, +13/-0)
  • src/plugins/loader.test.ts (modified, +88/-0)
  • src/plugins/registry.ts (modified, +29/-46)

Code Example

api.registerHook(
  ["before_tool_call", "after_tool_call"],
  (event, payload) => { /* ... */ }
);
// Missing: { name: "<plugin>-<purpose>", description: "..." }

---

[gateway] [plugins] hook registration missing name (plugin=<id>, source=...)
[gateway] [plugins] only memory plugins can register a memory capability (plugin=<id>, source=...)
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug — silent failure during plugin load.

Beta release blocker

No

Summary

The plugin loader silently drops register* calls that fail acceptance gates. The plugin still loads, the surface never fires, and the only signal is a level=warn diagnostic in gateway.log. Plugins ship green tests against helper-mock APIs and broken in production.

Steps to reproduce

In a plugin, register a hook without opts.name:

api.registerHook(
  ["before_tool_call", "after_tool_call"],
  (event, payload) => { /* ... */ }
);
// Missing: { name: "<plugin>-<purpose>", description: "..." }

Or call api.registerMemoryCapability(...) from a plugin whose manifest does not declare kind: "memory".

The api.registerX(...) call returns successfully. Helper-mock tests pass. The gateway loader logs:

[gateway] [plugins] hook registration missing name (plugin=<id>, source=...)
[gateway] [plugins] only memory plugins can register a memory capability (plugin=<id>, source=...)

…and silently drops the registration. The plugin loads with Status: loaded and the user has no in-band signal that anything is wrong.

Expected behavior

The loader should treat acceptance-gate failures as errors that either:

  1. Hard-fail the plugin loadStatus: error with the diagnostic surfaced via openclaw plugins doctor, or
  2. Throw synchronously from the api.registerX(...) call so the plugin's own error path runs (so unit tests can catch it via expect(...).toThrow)

Either is fine; the current "silent drop, log warn, plugin still loads" is the worst of both worlds — the plugin author has no defect signal until production. Helper-mock unit tests against a fake api cannot catch these gates because the mock has no gate logic.

Actual behavior

Plugin loads successfully. Surface never wires up. The only signal is a level=warn diagnostic in gateway.log that nothing in CI inspects.

Why this matters

Two real bugs hit our plugin (1fanwang/openclaw-lifework):

  • PR #38 registered a before_tool_call/after_tool_call telemetry hook without opts.name. Helper test passed. Gateway dropped the hook. Telemetry was disabled in production for weeks before we noticed (it was caught while debugging an unrelated dashboard issue).
  • PR #40 registered registerMemoryCapability(...) without declaring kind: "memory" in the manifest. Helper test passed. Gateway rejected the registration at every load. Memory artifacts were never indexed.

Both fixes are trivial once you know about them. The cost is the time-to-detect — which is open-ended without a hard-fail surface.

Proposed fix shape

In loader-*.js registerHook and the gated memory-* registrations, replace pushDiagnostic({ level: "warn"|"error", ... }) + early return with one of:

  • A throw from api.registerX(...) itself (preserves backwards-compat for plugins that already pass valid args; fails loud for plugins that don't)
  • A plugin-status downgrade (Status: error_partial or similar) that openclaw plugins inspect surfaces and CI can grep for

Workaround we're shipping today

A doctor-check that scans gateway.log + gateway.err.log since the most recent gateway] ready event, counts plugin=<id> warnings, and fails CI if non-zero. See scripts/doctor.sh in openclaw-lifework. This works but is per-plugin; an upstream fix would benefit every plugin author.

OpenClaw version

2026.4.24 (cbcfdf6). Source verified against installed /opt/homebrew/lib/node_modules/openclaw/dist/loader-NucjcOgv.js.

extent analysis

TL;DR

The plugin loader should treat acceptance-gate failures as errors, either hard-failing the plugin load or throwing synchronously from the api.registerX(...) call.

Guidance

  • Review the registerHook and registerMemoryCapability functions in loader-*.js to ensure they handle acceptance-gate failures as errors.
  • Consider replacing pushDiagnostic with a throw or plugin-status downgrade to surface errors to plugin authors.
  • Verify that the proposed fix does not break backwards-compatibility for plugins that pass valid arguments.
  • Test the fix with the provided reproduction steps to ensure it correctly handles acceptance-gate failures.

Example

// Proposed fix: throw from api.registerX(...) itself
api.registerHook = (hooks, callback, opts) => {
  if (!opts.name) {
    throw new Error('Hook registration missing name');
  }
  // ...
};

Notes

The current implementation silently drops registrations that fail acceptance gates, making it difficult for plugin authors to detect issues. A fix that surfaces these errors will improve the development experience and reduce time-to-detect for bugs.

Recommendation

Apply a workaround, such as the doctor-check that scans gateway.log + gateway.err.log for plugin warnings, until an upstream fix is available. This will provide a temporary solution for plugin authors to detect acceptance-gate failures.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The loader should treat acceptance-gate failures as errors that either:

  1. Hard-fail the plugin loadStatus: error with the diagnostic surfaced via openclaw plugins doctor, or
  2. Throw synchronously from the api.registerX(...) call so the plugin's own error path runs (so unit tests can catch it via expect(...).toThrow)

Either is fine; the current "silent drop, log warn, plugin still loads" is the worst of both worlds — the plugin author has no defect signal until production. Helper-mock unit tests against a fake api cannot catch these gates because the mock has no gate logic.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING