Bug type
Behavior bug (silent tool loss without crash)
Summary
resolvePluginToolRegistry in tools-D9CRirqH.js calls getLoadedRuntimePluginRegistry, which delegates to registryContainsPluginIds as an all-or-nothing gate: if ANY id in the agent's onlyPluginIds is not in the loaded registry, the entire lookup returns undefined and tools from plugins that ARE loaded get silently dropped.
In practice, the agent's onlyPluginIds is computed from its capability map and routinely includes plugins the user has not installed (e.g. firecrawl, tavily, file-transfer, llm-task, skill-workshop). One missing id ⇒ all per-plugin tools dropped, including memory_store/memory_recall/memory_forget from a fully-loaded memory-lancedb.
Observed in the wild
- OpenClaw: 2026.5.2
- Plugin:
@openclaw/[email protected] (memory slot)
- Symptom:
memory-lancedb registers, initializes, performs auto-recall/auto-capture successfully, and [plugins] memory-lancedb: plugin registered appears in startup. But memory_store/memory_recall/memory_forget never appear in the agent's tool list.
- Agent's
onlyPluginIds: ["browser","file-transfer","firecrawl","llm-task","memory-core","memory-lancedb","memory-wiki","skill-workshop","tavily"]
- Loaded plugins:
["active-memory","browser","memory-wiki","memory-lancedb","whatsapp"]
- Result:
getLoadedRuntimePluginRegistry returns undefined for both surface: "channel" and surface: "active" because file-transfer, firecrawl, llm-task, memory-core, skill-workshop, tavily are not loaded.
Root cause
active-runtime-registry-DkOuNyXa.js:
function registryContainsPluginIds(registry, pluginIds) {
if (pluginIds === void 0) return true;
const loaded = new Set();
for (const plugin of registry.plugins ?? [])
if (plugin.status === void 0 || plugin.status === "loaded") loaded.add(plugin.id);
// ... also scans Object.values(registry) array entries with .pluginId ...
if (pluginIds.length === 0) return loaded.size === 0;
return pluginIds.every((pluginId) => loaded.has(pluginId)); // ← strict
}
tools-D9CRirqH.js:resolvePluginToolRegistry:
const r1 = getLoadedRuntimePluginRegistry({ ...lookup, surface: "channel" });
const r2 = r1 ? null : getLoadedRuntimePluginRegistry({ ..., surface: "active" });
return r1 ?? r2; // both miss → returns undefined → all tools dropped
Verified with debug logging: the active registry does contain memory-lancedb with status: "loaded" and does have its 3 tools registered. The lookup just refuses to return the registry because other ids in the wishlist are absent.
Steps to reproduce
- Install only a subset of plugins (e.g.
memory-lancedb, whatsapp, browser, active-memory).
- Use the default
main agent (or any agent whose capability map references plugins not installed).
- Set
plugins.slots.memory = "memory-lancedb", enable the plugin, configure embedding.
- Send any agent message and ask it to list its tools.
- Observe:
memory_* tools missing from the list, despite memory-lancedb being fully loaded and operational (auto-recall still works because that path doesn't go through resolvePluginToolRegistry).
Suggested fix
Make the strict lookup retry with the intersection of requested + loaded ids when it misses. Minimal patch in tools-D9CRirqH.js:resolvePluginToolRegistry:
let result = r1 ?? r2;
if (!result && params.onlyPluginIds?.length) {
const ar = getActiveRuntimePluginRegistry();
if (ar) {
const loadedIds = new Set((ar.plugins ?? [])
.filter(p => p.status === undefined || p.status === "loaded")
.map(p => p.id ?? p.pluginId));
const intersection = params.onlyPluginIds.filter(id => loadedIds.has(id));
if (intersection.length > 0) {
const r3 = getLoadedRuntimePluginRegistry({
env: lookup.env,
workspaceDir: lookup.workspaceDir,
requiredPluginIds: intersection,
surface: "active",
});
if (r3) result = r3;
}
}
}
return result;
Alternative (cleaner): change registryContainsPluginIds's contract — return a partial-match result and let callers decide. The current "all-or-nothing" semantic makes onlyPluginIds behave as a hard requirement instead of a wishlist, which is wrong whenever the agent capability map references optional plugins.
Verified fix outcome
After applying the patch (locally to dist/):
- Agent tool list now includes
memory_store, memory_recall, memory_forget ✅
- End-to-end smoke test:
memory_store("foo") → memory_recall("foo") returns the stored item ✅
- No new warnings or errors at startup ✅
Environment
- OpenClaw: 2026.5.2
- OS: Ubuntu Linux
- Node.js: v24+ (tested on v25.2.1)
- Model: deepseek/deepseek-v4-flash (model-agnostic — registration-time bug)
Related
Bug type
Behavior bug (silent tool loss without crash)
Summary
resolvePluginToolRegistryintools-D9CRirqH.jscallsgetLoadedRuntimePluginRegistry, which delegates toregistryContainsPluginIdsas an all-or-nothing gate: if ANY id in the agent'sonlyPluginIdsis not in the loaded registry, the entire lookup returnsundefinedand tools from plugins that ARE loaded get silently dropped.In practice, the agent's
onlyPluginIdsis computed from its capability map and routinely includes plugins the user has not installed (e.g.firecrawl,tavily,file-transfer,llm-task,skill-workshop). One missing id ⇒ all per-plugin tools dropped, includingmemory_store/memory_recall/memory_forgetfrom a fully-loadedmemory-lancedb.Observed in the wild
@openclaw/[email protected](memory slot)memory-lancedbregisters, initializes, performs auto-recall/auto-capture successfully, and[plugins] memory-lancedb: plugin registeredappears in startup. Butmemory_store/memory_recall/memory_forgetnever appear in the agent's tool list.onlyPluginIds:["browser","file-transfer","firecrawl","llm-task","memory-core","memory-lancedb","memory-wiki","skill-workshop","tavily"]["active-memory","browser","memory-wiki","memory-lancedb","whatsapp"]getLoadedRuntimePluginRegistryreturnsundefinedfor bothsurface: "channel"andsurface: "active"becausefile-transfer,firecrawl,llm-task,memory-core,skill-workshop,tavilyare not loaded.Root cause
active-runtime-registry-DkOuNyXa.js:tools-D9CRirqH.js:resolvePluginToolRegistry:Verified with debug logging: the active registry does contain
memory-lancedbwithstatus: "loaded"and does have its 3 tools registered. The lookup just refuses to return the registry because other ids in the wishlist are absent.Steps to reproduce
memory-lancedb,whatsapp,browser,active-memory).mainagent (or any agent whose capability map references plugins not installed).plugins.slots.memory = "memory-lancedb", enable the plugin, configure embedding.memory_*tools missing from the list, despitememory-lancedbbeing fully loaded and operational (auto-recall still works because that path doesn't go throughresolvePluginToolRegistry).Suggested fix
Make the strict lookup retry with the intersection of requested + loaded ids when it misses. Minimal patch in
tools-D9CRirqH.js:resolvePluginToolRegistry:Alternative (cleaner): change
registryContainsPluginIds's contract — return a partial-match result and let callers decide. The current "all-or-nothing" semantic makesonlyPluginIdsbehave as a hard requirement instead of a wishlist, which is wrong whenever the agent capability map references optional plugins.Verified fix outcome
After applying the patch (locally to
dist/):memory_store, memory_recall, memory_forget✅memory_store("foo") → memory_recall("foo")returns the stored item ✅Environment
Related