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.
Bug Description
When
models.modeis 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 generatedmodels.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.jsonstill contains thecodexprovider from the bundledcodexplugin:{ "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 inmodels.json. Thecodexprovider (or any other bundled plugin providers) should be excluded unless explicitly configured.Root Cause Analysis
Why only
codexis affected (notanthropicor other bundled plugins)After further investigation, I found that not all bundled plugins are affected by this bug. The key difference is:
codexplugin (extensions/codex/provider.ts) explicitly defines acataloghook:anthropicplugin (extensions/anthropic/register.runtime.ts) usesapi.registerProvider()but does NOT define acatalogproperty:The
resolveImplicitProvidersfunction insrc/agents/models-config.providers.implicit.tsonly discovers providers that have acatalog(or legacydiscovery) hook defined. Sincecodexhas acataloghook andanthropicdoes not, onlycodexmodels are auto-injected.The actual bug in
models.mode: replacelogicIn
src/agents/models-config.plan.ts, the functionresolveProvidersForModelsJsonWithDepsalways callsresolveImplicitProvidersto discover providers from bundled plugins, then merges them with explicit providers:The
resolveProvidersForModefunction only controls whether to merge with existingmodels.jsonsecrets, but does NOT prevent implicit providers from being loaded:Schema confirmation
The
"replace"mode is officially supported in the schema (src/config/zod-schema.core.ts):With default being
"merge"(multiple locations usecfg.models?.mode ?? "merge").Proposed Fix
When
mode: "replace"is set, the code should skip callingresolveImplicitProvidersentirely and only use explicitly configured providers:Environment
Additional Context
The
codexplugin is a bundled plugin that provides models through itscataloghook. Even though the user never configured thecodexprovider, it appears inmodels.jsondue to this bug. Other bundled plugins likeanthropicare not affected because they don't define acataloghook.