fix: inject pluginConfig into hook handler event context#72888
Conversation
Greptile SummaryThis PR fixes a bug where
Confidence Score: 3/5Hold — the wrapper mutates the shared event context in-place, contaminating other handlers in the same dispatch loop. One P1 defect: the wrappedHandler mutates the shared event object instead of creating a shallow copy, which causes cross-handler context contamination at runtime. The overall plumbing change is correct and the fix is small, so this does not rise to P0, but the mutation bug needs to be addressed before merging. src/plugins/registry.ts — specifically the wrappedHandler block around lines 505–510. Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/plugins/registry.ts
Line: 505-510
Comment:
**Shared event object mutated in-place**
`triggerInternalHook` passes the **same** `event` reference to every registered handler sequentially (see `internal-hooks.ts` lines 296–305). Mutating `evt.context` here permanently writes `pluginConfig` onto the live event object before any subsequent handler (including non-plugin internal handlers and other plugins' wrapped handlers) receives it. If Plugin A and Plugin B both hook the same event, Plugin A's value is silently overwritten, and all non-plugin handlers that run after either plugin will see an unexpected `pluginConfig` key that does not belong to their context type.
Shallow-copy the event and its context instead of mutating the incoming object:
```suggestion
const wrappedHandler: typeof handler = async (evt) => {
return handler({
...evt,
context: { ...evt.context, pluginConfig },
});
};
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix: inject pluginConfig into hook handl..." | Re-trigger Greptile |
| const wrappedHandler: typeof handler = async (evt) => { | ||
| if (evt.context && typeof evt.context === "object") { | ||
| (evt.context as Record<string, unknown>).pluginConfig = pluginConfig; | ||
| } | ||
| return handler(evt); | ||
| }; |
There was a problem hiding this comment.
Shared event object mutated in-place
triggerInternalHook passes the same event reference to every registered handler sequentially (see internal-hooks.ts lines 296–305). Mutating evt.context here permanently writes pluginConfig onto the live event object before any subsequent handler (including non-plugin internal handlers and other plugins' wrapped handlers) receives it. If Plugin A and Plugin B both hook the same event, Plugin A's value is silently overwritten, and all non-plugin handlers that run after either plugin will see an unexpected pluginConfig key that does not belong to their context type.
Shallow-copy the event and its context instead of mutating the incoming object:
| const wrappedHandler: typeof handler = async (evt) => { | |
| if (evt.context && typeof evt.context === "object") { | |
| (evt.context as Record<string, unknown>).pluginConfig = pluginConfig; | |
| } | |
| return handler(evt); | |
| }; | |
| const wrappedHandler: typeof handler = async (evt) => { | |
| return handler({ | |
| ...evt, | |
| context: { ...evt.context, pluginConfig }, | |
| }); | |
| }; |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/registry.ts
Line: 505-510
Comment:
**Shared event object mutated in-place**
`triggerInternalHook` passes the **same** `event` reference to every registered handler sequentially (see `internal-hooks.ts` lines 296–305). Mutating `evt.context` here permanently writes `pluginConfig` onto the live event object before any subsequent handler (including non-plugin internal handlers and other plugins' wrapped handlers) receives it. If Plugin A and Plugin B both hook the same event, Plugin A's value is silently overwritten, and all non-plugin handlers that run after either plugin will see an unexpected `pluginConfig` key that does not belong to their context type.
Shallow-copy the event and its context instead of mutating the incoming object:
```suggestion
const wrappedHandler: typeof handler = async (evt) => {
return handler({
...evt,
context: { ...evt.context, pluginConfig },
});
};
```
How can I resolve this? If you propose a fix, please make it concise.
CI Failure AnalysisBoth failing checks are unrelated to this PR:
All plugin-adjacent checks passed:
|
13ceff7 to
aa81327
Compare
d7b2a73 to
a574c70
Compare
When plugins register hooks via api.registerHook(), pluginConfig from openclaw.json was not available in the hook event context. Plugins that accessed ctx.pluginConfig or event.context.pluginConfig received undefined, causing silent failures or fallback to defaults. Changes: - Add pluginConfig parameter to registerHook() function - Wrap handler to inject pluginConfig into event.context before invocation - Pass params.pluginConfig through createApi() call site Fixes #72880
Address review feedback on PR #72888. triggerInternalHook passes the same event reference to all handlers sequentially. Mutating evt.context leaks pluginConfig to subsequent handlers and causes cross-plugin overwrites. Shallow-copy event and context instead.
a574c70 to
0980f7a
Compare
Address review feedback on PR #72888. triggerInternalHook passes the same event reference to all handlers sequentially. Mutating evt.context leaks pluginConfig to subsequent handlers and causes cross-plugin overwrites. Shallow-copy event and context instead.
Address review feedback on PR openclaw#72888. triggerInternalHook passes the same event reference to all handlers sequentially. Mutating evt.context leaks pluginConfig to subsequent handlers and causes cross-plugin overwrites. Shallow-copy event and context instead.
Address review feedback on PR openclaw#72888. triggerInternalHook passes the same event reference to all handlers sequentially. Mutating evt.context leaks pluginConfig to subsequent handlers and causes cross-plugin overwrites. Shallow-copy event and context instead.
Address review feedback on PR openclaw#72888. triggerInternalHook passes the same event reference to all handlers sequentially. Mutating evt.context leaks pluginConfig to subsequent handlers and causes cross-plugin overwrites. Shallow-copy event and context instead.
Address review feedback on PR openclaw#72888. triggerInternalHook passes the same event reference to all handlers sequentially. Mutating evt.context leaks pluginConfig to subsequent handlers and causes cross-plugin overwrites. Shallow-copy event and context instead.
Address review feedback on PR openclaw#72888. triggerInternalHook passes the same event reference to all handlers sequentially. Mutating evt.context leaks pluginConfig to subsequent handlers and causes cross-plugin overwrites. Shallow-copy event and context instead.
Address review feedback on PR openclaw#72888. triggerInternalHook passes the same event reference to all handlers sequentially. Mutating evt.context leaks pluginConfig to subsequent handlers and causes cross-plugin overwrites. Shallow-copy event and context instead.
Problem
When plugins register hooks via
api.registerHook(), thepluginConfigfromopenclaw.jsonis not injected into the hook event context. Any plugin that accessesevent.context.pluginConfiginside a hook handler receivesundefined.Root Cause
In
src/plugins/registry.ts:registerHook()(line 419) passes the handler directly toregisterInternalHook(event, handler)without wrapping — nopluginConfigis added to the event contextcreateApi()(line 1469) passesparams.configbut notparams.pluginConfigtoregisterHookImpact
ctx.pluginConfigin hook handlers silently getundefinedctx.pluginConfig ?? {}) ignore user configurationapi.pluginConfigis available at registration time but NOT at hook invocation timeFix
pluginConfigparameter toregisterHook()pluginConfigintoevent.contextbefore each invocationparams.pluginConfigthrough fromcreateApi()Testing
Verified locally on v2026.4.22 with a custom plugin (
ava-bootstrap-inject) that readspluginConfig.filesfrom the hook context. Before patch:undefined→ plugin was a silent no-op. After patch: config resolves correctly and files are injected.Debug log confirming fix:
Fixes #72880