Skip to content

Bug: models.mode: replace does not exclude implicit providers from bundled plugins #68965

Description

@Tony-ooo

Bug Description

When models.mode is set to "replace", the configuration should only use explicitly configured providers and exclude all implicit providers discovered from bundled plugins. However, the current implementation still includes implicit providers (e.g., codex) in the generated models.json.

Current Behavior

Given this configuration:

{
  "models": {
    "mode": "replace",
    "providers": {
      "bailian": {
        "baseUrl": "https://coding.dashscope.aliyuncs.com/v1",
        "api": "openai-completions",
        "models": [...]
      }
    }
  }
}

The generated models.json still contains the codex provider from the bundled codex plugin:

{
  "providers": {
    "codex": {
      "baseUrl": "https://chatgpt.com/backend-api/v1",
      "api": "openai-codex-responses",
      "models": [...]
    },
    "bailian": { ... }
  }
}

Expected Behavior

When models.mode: "replace" is set, only explicitly configured providers should appear in models.json. The codex provider (or any other bundled plugin providers) should be excluded unless explicitly configured.

Root Cause Analysis

Why only codex is affected (not anthropic or other bundled plugins)

After further investigation, I found that not all bundled plugins are affected by this bug. The key difference is:

codex plugin (extensions/codex/provider.ts) explicitly defines a catalog hook:

export function buildCodexProvider(options: BuildCodexProviderOptions = {}): ProviderPlugin {
  return {
    id: PROVIDER_ID,
    // ...
    catalog: {
      order: "late",
      run: async (ctx) => buildCodexProviderCatalog({...}),
    },
    // ...
  };
}

anthropic plugin (extensions/anthropic/register.runtime.ts) uses api.registerProvider() but does NOT define a catalog property:

api.registerProvider({
  id: providerId,
  label: "Anthropic",
  // ...
  // No catalog property defined!
});

The resolveImplicitProviders function in src/agents/models-config.providers.implicit.ts only discovers providers that have a catalog (or legacy discovery) hook defined. Since codex has a catalog hook and anthropic does not, only codex models are auto-injected.

The actual bug in models.mode: replace logic

In src/agents/models-config.plan.ts, the function resolveProvidersForModelsJsonWithDeps always calls resolveImplicitProviders to discover providers from bundled plugins, then merges them with explicit providers:

const implicitProviders = await resolveImplicitProvidersImpl({...});
const explicitProviders = cfg.models?.providers ?? {};
return mergeProviders({
  implicit: implicitProviders,  // Always included!
  explicit: explicitProviders,
});

The resolveProvidersForMode function only controls whether to merge with existing models.json secrets, but does NOT prevent implicit providers from being loaded:

function resolveProvidersForMode(params: {
  mode: NonNullable<ModelsConfig["mode"]>;
  ...
}): Record<string, ProviderConfig> {
  if (params.mode !== "merge") {
    return params.providers;  // Already contains implicit providers!
  }
  ...
}

Schema confirmation

The "replace" mode is officially supported in the schema (src/config/zod-schema.core.ts):

mode: z.union([z.literal("merge"), z.literal("replace")]).optional()

With default being "merge" (multiple locations use cfg.models?.mode ?? "merge").

Proposed Fix

When mode: "replace" is set, the code should skip calling resolveImplicitProviders entirely and only use explicitly configured providers:

// In resolveProvidersForModelsJsonWithDeps
if (cfg.models?.mode === "replace") {
  return explicitProviders;  // Skip implicit provider discovery
}
// Otherwise, discover and merge implicit providers
const implicitProviders = await resolveImplicitProvidersImpl({...});
return mergeProviders({ implicit: implicitProviders, explicit: explicitProviders });

Environment

  • OpenClaw Version: 2026.4.12
  • Git Commit: 1c0672b
  • OS: macOS (Darwin arm64)

Additional Context

The codex plugin is a bundled plugin that provides models through its catalog hook. Even though the user never configured the codex provider, it appears in models.json due to this bug. Other bundled plugins like anthropic are not affected because they don't define a catalog hook.

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