diff --git a/src/commands/models.list.e2e.test.ts b/src/commands/models.list.e2e.test.ts index 53a112d0451..171893134a1 100644 --- a/src/commands/models.list.e2e.test.ts +++ b/src/commands/models.list.e2e.test.ts @@ -324,7 +324,19 @@ describe("models list/status", () => { await expect(loadModelRegistry({})).rejects.toThrow("model discovery unavailable"); }); - it("loadModelRegistry persists using source config snapshot when provided", async () => { + it("loadModelRegistry does not persist models.json as a side effect", async () => { + modelRegistryState.models = [OPENAI_MODEL]; + modelRegistryState.available = [OPENAI_MODEL]; + const resolvedConfig = { + models: { providers: { openai: { apiKey: "sk-resolved-runtime-value" } } }, // pragma: allowlist secret + }; + + await loadModelRegistry(resolvedConfig as never); + + expect(ensureOpenClawModelsJson).not.toHaveBeenCalled(); + }); + + it("modelsListCommand persists using the write snapshot config when provided", async () => { modelRegistryState.models = [OPENAI_MODEL]; modelRegistryState.available = [OPENAI_MODEL]; const sourceConfig = { @@ -333,21 +345,14 @@ describe("models list/status", () => { const resolvedConfig = { models: { providers: { openai: { apiKey: "sk-resolved-runtime-value" } } }, // pragma: allowlist secret }; + readConfigFileSnapshotForWrite.mockResolvedValue({ + snapshot: { valid: true, resolved: resolvedConfig, source: sourceConfig }, + writeOptions: {}, + }); + setDefaultModel("openai/gpt-4.1-mini"); + const runtime = makeRuntime(); - await loadModelRegistry(resolvedConfig as never, { sourceConfig: sourceConfig as never }); - - expect(ensureOpenClawModelsJson).toHaveBeenCalledTimes(1); - expect(ensureOpenClawModelsJson).toHaveBeenCalledWith(sourceConfig); - }); - - it("loadModelRegistry uses resolved config when no source snapshot is provided", async () => { - modelRegistryState.models = [OPENAI_MODEL]; - modelRegistryState.available = [OPENAI_MODEL]; - const resolvedConfig = { - models: { providers: { openai: { apiKey: "sk-resolved-runtime-value" } } }, // pragma: allowlist secret - }; - - await loadModelRegistry(resolvedConfig as never); + await modelsListCommand({ all: true, json: true }, runtime); expect(ensureOpenClawModelsJson).toHaveBeenCalledTimes(1); expect(ensureOpenClawModelsJson).toHaveBeenCalledWith(resolvedConfig); diff --git a/src/commands/models/list.list-command.ts b/src/commands/models/list.list-command.ts index afcd7b785d2..acb6c95761f 100644 --- a/src/commands/models/list.list-command.ts +++ b/src/commands/models/list.list-command.ts @@ -23,6 +23,7 @@ export async function modelsListCommand( ) { ensureFlagCompatibility(opts); const { ensureAuthProfileStore } = await import("../../agents/auth-profiles.js"); + const { ensureOpenClawModelsJson } = await import("../../agents/models-config.js"); const { sourceConfig, resolvedConfig: cfg } = await loadModelsConfigWithSource({ commandName: "models list", runtime, @@ -42,6 +43,9 @@ export async function modelsListCommand( let availableKeys: Set | undefined; let availabilityErrorMessage: string | undefined; try { + // Keep command behavior explicit: sync models.json from the source config + // before building the read-only model registry view. + await ensureOpenClawModelsJson(sourceConfig ?? cfg); const loaded = await loadModelRegistry(cfg, { sourceConfig }); modelRegistry = loaded.registry; models = loaded.models; diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index 187b55176f5..340d49155df 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -8,7 +8,6 @@ import { resolveAwsSdkEnvVarName, resolveEnvApiKey, } from "../../agents/model-auth.js"; -import { ensureOpenClawModelsJson } from "../../agents/models-config.js"; import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js"; import type { OpenClawConfig } from "../../config/config.js"; import { @@ -95,12 +94,9 @@ function loadAvailableModels(registry: ModelRegistry): Model[] { } export async function loadModelRegistry( - cfg: OpenClawConfig, - opts?: { sourceConfig?: OpenClawConfig }, + _cfg: OpenClawConfig, + _opts?: { sourceConfig?: OpenClawConfig }, ) { - // Persistence must be based on source config (pre-resolution) so SecretRef-managed - // credentials remain markers in models.json for command paths too. - await ensureOpenClawModelsJson(opts?.sourceConfig ?? cfg); const agentDir = resolveOpenClawAgentDir(); const authStorage = discoverAuthStorage(agentDir); const registry = discoverModels(authStorage, agentDir);