Summary
When plugins register hooks via api.registerHook(), the pluginConfig from openclaw.json is not injected into the hook event context. Any plugin that accesses ctx.pluginConfig or event.context.pluginConfig inside a hook handler receives undefined.
Affected Code
File: src/plugins/registry.ts
registerHook function (line 419): The handler is passed directly to registerInternalHook(event, handler) at line 502 without wrapping. The hook event context is built by createInternalHookEvent() which only sets workspaceDir, bootstrapFiles, cfg, sessionKey, sessionId, agentId — no pluginConfig.
createApi call site (line 1469): Passes params.config but not params.pluginConfig to registerHook.
Impact
- Any plugin using
api.registerHook() that needs to read its configured pluginConfig from the hook context will silently get undefined
api.pluginConfig is available at registration time but NOT at hook invocation time
- Plugins that fall back to defaults still work but ignore user configuration
- Plugins that guard on
ctx.pluginConfig being defined silently no-op
Reproduction
- Create a plugin with a config schema and some config in
openclaw.json
- Register a hook:
api.registerHook("agent:bootstrap", async (event) => { const cfg = event.context.pluginConfig; /* undefined */ })
- Observe that
pluginConfig is always undefined
Suggested Fix
Pass pluginConfig to registerHook and wrap the handler to inject it:
// registry.ts line 419 — add pluginConfig parameter
const registerHook = (
record: PluginRecord,
events: string | string[],
handler: Parameters<typeof registerInternalHook>[1],
opts: OpenClawPluginHookOptions | undefined,
config: OpenClawPluginApi["config"],
pluginConfig: unknown, // <-- add this
) => {
// ... existing code ...
// line 502 — wrap handler to inject pluginConfig
for (const event of normalizedEvents) {
const wrappedHandler = async (evt: InternalHookEvent) => {
if (evt.context && typeof evt.context === "object") {
evt.context.pluginConfig = pluginConfig;
}
return handler(evt);
};
registerInternalHook(event, wrappedHandler);
nextRegistrations.push({ event, handler: wrappedHandler });
}
// line 1469 — pass pluginConfig
registerHook(record, events, handler, opts, params.config, params.pluginConfig),
Workaround
Plugins can capture api.pluginConfig at registration time via closure:
register(api) {
const config = api.pluginConfig;
api.registerHook("agent:bootstrap", async (event) => {
const cfg = event.context.pluginConfig ?? config; // fallback to closure
});
}
Versions
- Reproduced on: 2026.4.22, 2026.4.25
- Confirmed on
main branch (registry.ts)
Summary
When plugins register hooks via
api.registerHook(), thepluginConfigfromopenclaw.jsonis not injected into the hook event context. Any plugin that accessesctx.pluginConfigorevent.context.pluginConfiginside a hook handler receivesundefined.Affected Code
File:
src/plugins/registry.tsregisterHookfunction (line 419): The handler is passed directly toregisterInternalHook(event, handler)at line 502 without wrapping. The hook event context is built bycreateInternalHookEvent()which only setsworkspaceDir,bootstrapFiles,cfg,sessionKey,sessionId,agentId— nopluginConfig.createApicall site (line 1469): Passesparams.configbut notparams.pluginConfigtoregisterHook.Impact
api.registerHook()that needs to read its configuredpluginConfigfrom the hook context will silently getundefinedapi.pluginConfigis available at registration time but NOT at hook invocation timectx.pluginConfigbeing defined silently no-opReproduction
openclaw.jsonapi.registerHook("agent:bootstrap", async (event) => { const cfg = event.context.pluginConfig; /* undefined */ })pluginConfigis alwaysundefinedSuggested Fix
Pass
pluginConfigtoregisterHookand wrap the handler to inject it:Workaround
Plugins can capture
api.pluginConfigat registration time via closure:Versions
mainbranch (registry.ts)