Summary
When Gateway starts, plugins are loaded twice with different preferSetupRuntimeForChannelPlugins values, causing different cacheKeys. This results in tool registration being skipped on the second load due to pluginRegistrationState not being reset.
Environment
- OpenClaw version: 2026.3.28
- Plugin: OpenViking (context-engine type)
- Node: v22.22.0
- OS: Linux (Debian 13)
Repro
-
Install OpenViking plugin:
openclaw plugins install openviking
-
Configure plugins.slots.contextEngine and plugins.entries.openviking.enabled: true
-
Add tool names to agent allowlist:
"agents": {
"list": [{
"id": "main",
"tools": {
"alsoAllow": ["memory_recall", "memory_store", "memory_forget"]
}
}]
}
-
Restart gateway:
-
Check logs:
allowlist contains unknown entries (memory_recall, memory_store, memory_forget)
Expected
Plugin tools should be registered and available to the agent session.
Actual
- Context-engine is registered successfully
- Plugin tools are NOT registered
- Warning:
allowlist contains unknown entries (memory_recall, memory_store, memory_forget)
Root Cause Analysis
Gateway Startup Flow
Gateway calls loadOpenClawPlugins twice:
-
First call: loadGatewayStartupPlugins({ preferSetupRuntimeForChannelPlugins: true })
- cacheKey mode:
"prefer-setup"
- Plugin registers context-engine and tools
pluginRegistrationState: "idle" → "registering" → "registered"
-
Second call: reloadDeferredGatewayPlugins({}) (no preferSetupRuntimeForChannelPlugins)
- cacheKey mode:
"full"
- Different cacheKey → cache miss → plugin loaded again
pluginRegistrationState already "registered"
beginPluginRegistration() returns null → tool registration skipped
CacheKey Calculation
const startupChannelMode = params.preferSetupRuntimeForChannelPlugins === true ? "prefer-setup" : "full";
The cacheKey differs between the two calls, causing the plugin to be loaded twice.
Plugin State Management
let pluginRegistrationState: PluginRegistrationState = "idle";
function beginPluginRegistration(api: OpenClawPluginApi): number | null {
if (pluginRegistrationState !== "idle") {
api.logger.info("plugin registration already active, skipping");
return null; // <-- Tool registration skipped on second load
}
pluginRegistrationState = "registering";
// ...
}
The module-level pluginRegistrationState is not reset when the plugin is loaded again with a different cacheKey.
Evidence
Log Timeline
11:02:23 - weixin plugin loaded (setup-runtime mode)
11:02:25 - OpenViking: registered context-engine (first load)
11:02:27 - weixin plugin loaded (full mode)
11:02:27 - OpenViking: registered context-engine (second load)
11:02:43 - openviking: plugin registration already active, skipping duplicate registration
11:02:43 - allowlist contains unknown entries (memory_recall, memory_store, memory_forget)
Code Reference
loadGatewayStartupPlugins(): calls with preferSetupRuntimeForChannelPlugins: deferredConfiguredChannelPluginIds.length > 0
reloadDeferredGatewayPlugins(): does NOT pass preferSetupRuntimeForChannelPlugins
buildCacheKey(): includes startupChannelMode which differs between calls
Potential Fixes
-
Pass consistent preferSetupRuntimeForChannelPlugins to both loadGatewayStartupPlugins and reloadDeferredGatewayPlugins
-
Reset pluginRegistrationState when loading with a different cacheKey
-
Use cacheKey-aware state management instead of module-level variables
Related
Workaround
- Use
autoCapture and autoRecall features instead of manual tool invocation
- Call OpenViking API directly via HTTP
Summary
When Gateway starts, plugins are loaded twice with different
preferSetupRuntimeForChannelPluginsvalues, causing different cacheKeys. This results in tool registration being skipped on the second load due topluginRegistrationStatenot being reset.Environment
Repro
Install OpenViking plugin:
Configure
plugins.slots.contextEngineandplugins.entries.openviking.enabled: trueAdd tool names to agent allowlist:
Restart gateway:
Check logs:
Expected
Plugin tools should be registered and available to the agent session.
Actual
allowlist contains unknown entries (memory_recall, memory_store, memory_forget)Root Cause Analysis
Gateway Startup Flow
Gateway calls
loadOpenClawPluginstwice:First call:
loadGatewayStartupPlugins({ preferSetupRuntimeForChannelPlugins: true })"prefer-setup"pluginRegistrationState:"idle"→"registering"→"registered"Second call:
reloadDeferredGatewayPlugins({})(nopreferSetupRuntimeForChannelPlugins)"full"pluginRegistrationStatealready"registered"beginPluginRegistration()returnsnull→ tool registration skippedCacheKey Calculation
The cacheKey differs between the two calls, causing the plugin to be loaded twice.
Plugin State Management
The module-level
pluginRegistrationStateis not reset when the plugin is loaded again with a different cacheKey.Evidence
Log Timeline
Code Reference
loadGatewayStartupPlugins(): calls withpreferSetupRuntimeForChannelPlugins: deferredConfiguredChannelPluginIds.length > 0reloadDeferredGatewayPlugins(): does NOT passpreferSetupRuntimeForChannelPluginsbuildCacheKey(): includesstartupChannelModewhich differs between callsPotential Fixes
Pass consistent
preferSetupRuntimeForChannelPluginsto bothloadGatewayStartupPluginsandreloadDeferredGatewayPluginsReset
pluginRegistrationStatewhen loading with a different cacheKeyUse cacheKey-aware state management instead of module-level variables
Related
Workaround
autoCaptureandautoRecallfeatures instead of manual tool invocation