Skip to content

fix(plugins): preserve contextEngine slot through config normalization#64192

Merged
jalehman merged 5 commits into
openclaw:mainfrom
hclsys:fix/plugins-preserve-context-engine-slot
Apr 10, 2026
Merged

fix(plugins): preserve contextEngine slot through config normalization#64192
jalehman merged 5 commits into
openclaw:mainfrom
hclsys:fix/plugins-preserve-context-engine-slot

Conversation

@hclsys

@hclsys hclsys commented Apr 10, 2026

Copy link
Copy Markdown

Summary

normalizePluginsConfigWithResolver() in src/plugins/config-normalization-shared.ts reconstructed the slots object with only the memory key, silently dropping contextEngine. This caused slot-selected context-engine plugins to lose explicit-selection credit during loader diagnostics.

Fixes #64170

Symptom

{
  "plugins": {
    "allow": ["cortex", "eva-xt", "lossless-claw"],
    "slots": {
      "memory": "cortex",
      "contextEngine": "lossless-claw"
    }
  }
}

After normalization, slots.contextEngine is undefined. Loader diagnostics then treat the discovered context-engine plugin as not explicitly selected, and warnWhenAllowlistIsOpen() emits a false warning:

[plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load ...

Root cause

// config-normalization-shared.ts:143-145 (before fix)
slots: {
  memory: memorySlot === undefined ? defaultSlotIdForKey("memory") : memorySlot,
  // contextEngine: missing — silently dropped
},

The zod schema (zod-schema.ts:938) accepts contextEngine: z.string().optional(), and the raw config preserves it, but the normalization function discards it during reconstruction.

Fix

Add contextEngine to the normalized slots object using the same normalizeSlotValue helper already used for memory:

 slots: {
   memory: memorySlot === undefined ? defaultSlotIdForKey("memory") : memorySlot,
+  contextEngine: normalizeSlotValue(config?.slots?.contextEngine),
 },

And add contextEngine?: string | null to the NormalizedPluginsConfig type.

Unlike memory (which defaults to "memory-core" when absent), contextEngine has no default — it's undefined when not explicitly configured.

Tests

Added 5 parameterized cases in src/plugins/config-state.test.ts:

Input Expected
{} (absent) undefined
{ contextEngine: "lossless-claw" } "lossless-claw"
{ contextEngine: "none" } null
{ contextEngine: " cortex " } "cortex" (trimmed)
{ contextEngine: "" } undefined

Scope

  • Files: src/plugins/config-normalization-shared.ts (+2), src/plugins/config-state.test.ts (+10)
  • Production LOC: 2 lines (1 type field + 1 normalize call)
  • oxlint clean
  • Zero competition: no open PRs touch normalizePluginsConfigWithResolver or the contextEngine slot

Credit to @100yenadmin for the precise call-chain RCA in #64170.

@greptile-apps

greptile-apps Bot commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a real data-loss bug: normalizePluginsConfigWithResolver reconstructed slots with only the memory key, silently discarding contextEngine on every normalization pass. Adding contextEngine: normalizeSlotValue(config?.slots?.contextEngine) and the matching type field is correct and necessary.

The fix is incomplete relative to the stated symptom, however. resolveExplicitPluginSelection in both config-state.ts (line 231) and config-policy.ts (line 51) check only config.slots.memory for explicit-selection credit; there is no parallel branch for contextEngine. A workspace-origin plugin chosen via slots.contextEngine will still be gated behind workspace-disabled-by-default (and the allowlist-open warning) unless it also appears in plugins.allow.

Confidence Score: 4/5

Safe to merge as a data-preservation fix; the incomplete explicit-selection credit should be addressed before the contextEngine slot fully replaces the allow-list for workspace plugins.

The normalization fix itself is correct and the tests cover it well. Score is 4 rather than 5 because the PR description claims to fix explicit-selection credit for context-engine plugins, but neither resolveExplicitPluginSelection function is updated to check slots.contextEngine — leaving workspace-origin context-engine plugins still gated behind the allow list.

src/plugins/config-state.ts and src/plugins/config-policy.ts — both resolveExplicitPluginSelection functions need a contextEngine slot branch to complete the fix described in the PR.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/plugins/config-normalization-shared.ts
Line: 146

Comment:
**`resolveExplicitPluginSelection` never grants credit for `contextEngine` slot**

The normalization fix is correct, but `resolveExplicitPluginSelection` in both `src/plugins/config-state.ts` (line 231) and `src/plugins/config-policy.ts` (line 51) only checks `config.slots.memory` for explicit-selection credit — there is no corresponding branch for `config.slots.contextEngine`. Preserving the slot value through normalization is necessary, but insufficient: a workspace-origin context-engine plugin selected via `slots.contextEngine` will still be blocked at the `workspace-disabled-by-default` gate (line 291 in `config-state.ts`) unless the plugin is also listed in `allow`. Likewise, `warnWhenAllowlistIsOpen` is keyed entirely on `allow.length === 0`, so setting only `slots.contextEngine` still triggers the warning described in #64170.

If `contextEngine` slot selection is meant to count as explicit-selection credit (matching the semantics of `memory`), both `resolveExplicitPluginSelection` functions and the allowlist-open warning should be updated to account for it.

```ts
// config-state.ts resolveExplicitPluginSelection — example addition
if (params.config.slots.contextEngine === params.id) {
  return { explicitlyEnabled: true, cause: "selected-context-engine-slot" };
}
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(plugins): preserve contextEngine slo..." | Re-trigger Greptile

@@ -142,6 +143,7 @@ export function normalizePluginsConfigWithResolver(
loadPaths: normalizeList(config?.load?.paths, identityNormalizePluginId),
slots: {
memory: memorySlot === undefined ? defaultSlotIdForKey("memory") : memorySlot,
contextEngine: normalizeSlotValue(config?.slots?.contextEngine),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 resolveExplicitPluginSelection never grants credit for contextEngine slot

The normalization fix is correct, but resolveExplicitPluginSelection in both src/plugins/config-state.ts (line 231) and src/plugins/config-policy.ts (line 51) only checks config.slots.memory for explicit-selection credit — there is no corresponding branch for config.slots.contextEngine. Preserving the slot value through normalization is necessary, but insufficient: a workspace-origin context-engine plugin selected via slots.contextEngine will still be blocked at the workspace-disabled-by-default gate (line 291 in config-state.ts) unless the plugin is also listed in allow. Likewise, warnWhenAllowlistIsOpen is keyed entirely on allow.length === 0, so setting only slots.contextEngine still triggers the warning described in #64170.

If contextEngine slot selection is meant to count as explicit-selection credit (matching the semantics of memory), both resolveExplicitPluginSelection functions and the allowlist-open warning should be updated to account for it.

// config-state.ts resolveExplicitPluginSelection — example addition
if (params.config.slots.contextEngine === params.id) {
  return { explicitlyEnabled: true, cause: "selected-context-engine-slot" };
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/config-normalization-shared.ts
Line: 146

Comment:
**`resolveExplicitPluginSelection` never grants credit for `contextEngine` slot**

The normalization fix is correct, but `resolveExplicitPluginSelection` in both `src/plugins/config-state.ts` (line 231) and `src/plugins/config-policy.ts` (line 51) only checks `config.slots.memory` for explicit-selection credit — there is no corresponding branch for `config.slots.contextEngine`. Preserving the slot value through normalization is necessary, but insufficient: a workspace-origin context-engine plugin selected via `slots.contextEngine` will still be blocked at the `workspace-disabled-by-default` gate (line 291 in `config-state.ts`) unless the plugin is also listed in `allow`. Likewise, `warnWhenAllowlistIsOpen` is keyed entirely on `allow.length === 0`, so setting only `slots.contextEngine` still triggers the warning described in #64170.

If `contextEngine` slot selection is meant to count as explicit-selection credit (matching the semantics of `memory`), both `resolveExplicitPluginSelection` functions and the allowlist-open warning should be updated to account for it.

```ts
// config-state.ts resolveExplicitPluginSelection — example addition
if (params.config.slots.contextEngine === params.id) {
  return { explicitlyEnabled: true, cause: "selected-context-engine-slot" };
}
```

How can I resolve this? If you propose a fix, please make it concise.

@hclsys

This comment was marked as low quality.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7c32bea753

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@@ -231,6 +231,9 @@ function resolveExplicitPluginSelection(params: {
if (params.config.slots.memory === params.id) {
return { explicitlyEnabled: true, cause: "selected-memory-slot" };
}
if (params.config.slots.contextEngine === params.id) {
return { explicitlyEnabled: true, cause: "selected-context-engine-slot" };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add context-engine cause to activation cause tables

The new "selected-context-engine-slot" branch returns a cause string that is not part of PluginExplicitSelectionCause/PluginActivationCause and is also missing from PLUGIN_ACTIVATION_REASON_BY_CAUSE, so selecting a plugin via plugins.slots.contextEngine can produce an activation state with no mapped reason (and will fail in type-checked builds). Please add this cause to the union(s) and reason map so context-engine explicit selection is treated consistently with memory-slot selection.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1ad40f66f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +236 to +237
if (params.config.slots.contextEngine === params.id) {
return { explicitlyEnabled: true, cause: "selected-context-engine-slot" };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Hash contextEngine slot in activation cache metadata

This new contextEngine explicit-selection path makes activation depend on activationSource.plugins.slots.contextEngine, but buildActivationMetadataHash in src/plugins/loader.ts still hashes only memorySlot plus allow/deny/entries/channels. In flows that pass different activationSourceConfig and resolved config, changing only plugins.slots.contextEngine can reuse a stale cached registry and leave the wrong plugin activation state. Please include contextEngine in the activation metadata hash (and coverage) so registry cache invalidation matches the new activation input.

Useful? React with 👍 / 👎.

@jalehman
jalehman force-pushed the fix/plugins-preserve-context-engine-slot branch from a1ad40f to 1287394 Compare April 10, 2026 22:48
@jalehman
jalehman force-pushed the fix/plugins-preserve-context-engine-slot branch from 1287394 to 6457468 Compare April 10, 2026 22:50
@jalehman
jalehman force-pushed the fix/plugins-preserve-context-engine-slot branch 2 times, most recently from a424454 to d737c84 Compare April 10, 2026 22:56
hclsys and others added 5 commits April 10, 2026 15:57
normalizePluginsConfigWithResolver() reconstructed the `slots` object
with only the `memory` key, silently dropping `contextEngine`. This
caused slot-selected context-engine plugins to lose explicit-selection
credit during loader diagnostics, producing false `plugins.allow is
empty` warnings even when the on-disk config had a populated allowlist
and the context engine was selected via `plugins.slots.contextEngine`.

Fix: add `contextEngine: normalizeSlotValue(config?.slots?.contextEngine)`
to the returned `slots` object in config-normalization-shared.ts, and
add `contextEngine?: string | null` to the NormalizedPluginsConfig type.

The normalizeSlotValue helper is already used for the `memory` slot and
handles trimming, `"none" → null`, and empty → undefined.

Tests added in config-state.test.ts:
- contextEngine slot value is preserved through normalization
- "none" is normalized to null
- whitespace is trimmed
- empty/absent returns undefined (no default, unlike memory which
  defaults to "memory-core")

Fixes openclaw#64170
The prior commit preserved `contextEngine` through config normalization,
but `resolveExplicitPluginSelection` in both `config-state.ts` and
`config-policy.ts` only checked `config.slots.memory` for
explicit-selection credit. A context-engine plugin selected via
`slots.contextEngine` was still blocked at the
`workspace-disabled-by-default` gate and still triggered the
`plugins.allow is empty` warning.

Fix: add `config.slots.contextEngine === params.id` checks at all 4
selection sites (2 in config-state.ts, 2 in config-policy.ts), mirroring
the existing `memory` pattern with `cause: "selected-context-engine-slot"`
/ `reason: "selected context engine slot"`.

Addresses greptile 4/5 review feedback on openclaw#64192.
@jalehman
jalehman force-pushed the fix/plugins-preserve-context-engine-slot branch from d737c84 to ae8bd9f Compare April 10, 2026 22:58
@jalehman
jalehman merged commit 8a28a3b into openclaw:main Apr 10, 2026
8 checks passed
@jalehman

Copy link
Copy Markdown
Contributor

Merged via squash.

Thanks @hclsys!

yoloshii added a commit to yoloshii/ClawMem that referenced this pull request Apr 11, 2026
clawmem setup openclaw now auto-creates the symlink at
~/.openclaw/extensions/clawmem instead of only printing manual steps.
Handles stale symlinks, existing directories, and file conflicts.
--remove actually uninstalls. Fixes #5.

Plugin manifest renamed from plugin.json to openclaw.plugin.json so
the symlink exposes the correct filename without a copy step that
wrote into the package source tree.

Adds OpenClaw v2026.4.10+ version warning for the contextEngine slot
normalization bug (openclaw/openclaw#64192), troubleshooting entry,
and Active Memory coexistence documentation.

Codex review: session 019d72d5, turns 25-27, ~4.32M tokens.
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
openclaw#64192)

Merged via squash.

Prepared head SHA: ae8bd9f
Co-authored-by: hclsys <[email protected]>
Co-authored-by: jalehman <[email protected]>
Reviewed-by: @jalehman
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
openclaw#64192)

Merged via squash.

Prepared head SHA: ae8bd9f
Co-authored-by: hclsys <[email protected]>
Co-authored-by: jalehman <[email protected]>
Reviewed-by: @jalehman
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
openclaw#64192)

Merged via squash.

Prepared head SHA: ae8bd9f
Co-authored-by: hclsys <[email protected]>
Co-authored-by: jalehman <[email protected]>
Reviewed-by: @jalehman
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
openclaw#64192)

Merged via squash.

Prepared head SHA: ae8bd9f
Co-authored-by: hclsys <[email protected]>
Co-authored-by: jalehman <[email protected]>
Reviewed-by: @jalehman
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
openclaw#64192)

Merged via squash.

Prepared head SHA: ae8bd9f
Co-authored-by: hclsys <[email protected]>
Co-authored-by: jalehman <[email protected]>
Reviewed-by: @jalehman
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
openclaw#64192)

Merged via squash.

Prepared head SHA: ae8bd9f
Co-authored-by: hclsys <[email protected]>
Co-authored-by: jalehman <[email protected]>
Reviewed-by: @jalehman
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plugins: normalizePluginsConfig drops slots.contextEngine, causing false "plugins.allow is empty" warning

2 participants