Skip to content

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

Description

@100yenadmin

Summary

normalizePluginsConfigWithResolver() preserves plugins.slots.memory but silently drops plugins.slots.contextEngine from the normalized plugin config. That causes slot-selected context-engine plugins to lose explicit-selection credit during loader diagnostics.

One visible symptom is a false warning from warnWhenAllowlistIsOpen() saying plugins.allow is empty even when the on-disk config has a populated allowlist and the context engine was selected via plugins.slots.contextEngine.

Repro

Given config like:

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

At runtime:

  1. loadConfig() returns cfg.plugins.slots.contextEngine = "lossless-claw"
  2. normalizePluginsConfig(cfg.plugins) drops that field and returns only slots.memory
  3. loader diagnostics then treat the discovered non-bundled context-engine plugin as not explicitly selected
  4. warnWhenAllowlistIsOpen() can emit:
[plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load ...

Evidence / call trace

  • Config load: src/config/config.ts / runtime loadConfig() path
  • Plugin normalization entrypoint: src/plugins/config-state.ts:142
  • Buggy normalizer: src/plugins/config-normalization-shared.ts:133-145
  • Misleading warning: src/plugins/loader.ts:993-1021

Current normalizer only carries slots.memory:

const memorySlot = normalizeSlotValue(config?.slots?.memory);
return {
  ...
  slots: {
    memory: memorySlot === undefined ? defaultSlotIdForKey("memory") : memorySlot,
  },
  ...
};

But the schema/docs/tests already support plugins.slots.contextEngine, so this field is schema-valid input that gets lost later.

Blast radius

Non-blocking for many current setups, but still real:

  • false operator/security warnings about an "empty" allowlist
  • slot-selected context-engine plugins are under-credited as explicitly selected
  • any future logic that relies on normalized slots.contextEngine will silently misbehave

Proposed fix

Preserve contextEngine during normalization and slightly tighten the warning text so it describes the effective loader allowlist instead of the raw config.

diff --git a/src/plugins/config-normalization-shared.ts b/src/plugins/config-normalization-shared.ts
@@
 export type NormalizedPluginsConfig = {
@@
   slots: {
     memory?: string | null;
+    contextEngine?: string | null;
   };
@@
 ): NormalizedPluginsConfig {
   const memorySlot = normalizeSlotValue(config?.slots?.memory);
+  const contextEngineSlot = normalizeSlotValue(config?.slots?.contextEngine);
   return {
@@
     slots: {
       memory: memorySlot === undefined ? defaultSlotIdForKey("memory") : memorySlot,
+      ...(contextEngineSlot !== undefined ? { contextEngine: contextEngineSlot } : {}),
     },

and

diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts
@@
-  `[plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load: ...`
+  `[plugins] effective plugin allowlist is empty; discovered non-bundled plugins may auto-load: ...`

Verification

I verified the fix locally against source by adding a regression test in src/plugins/config-policy.test.ts that asserts contextEngine survives normalization, then running:

pnpm exec vitest run src/plugins/config-policy.test.ts src/plugins/slots.test.ts

Both files passed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions