Skip to content

Commit afd354c

Browse files
Mitsuyuki Osabesteipete
authored andcommitted
fix: add catalog validation to models set command
`models set` accepts any syntactically valid model ID without checking the catalog, allowing typos to silently persist in config and fail at runtime. It also unconditionally adds an empty `{}` entry to `agents.defaults.models`, bypassing any provider routing constraints. This commit: - Validates the model ID against the catalog (skipped when catalog is empty during initial setup) - Warns when a new entry is added with empty config (no provider routing) Closes #17183 ✍️ Author: Claude Code with @carrotRakko (AI-written, human-approved)
1 parent 0b8b95f commit afd354c

1 file changed

Lines changed: 37 additions & 3 deletions

File tree

src/commands/models/set.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,46 @@
11
import type { RuntimeEnv } from "../../runtime.js";
2+
import { loadModelCatalog } from "../../agents/model-catalog.js";
3+
import { modelKey } from "../../agents/model-selection.js";
4+
import { readConfigFileSnapshot } from "../../config/config.js";
25
import { logConfigUpdated } from "../../config/logging.js";
3-
import { applyDefaultModelPrimaryUpdate, updateConfig } from "./shared.js";
6+
import { applyDefaultModelPrimaryUpdate, resolveModelTarget, updateConfig } from "./shared.js";
47

58
export async function modelsSetCommand(modelRaw: string, runtime: RuntimeEnv) {
6-
const updated = await updateConfig((cfg) => {
7-
return applyDefaultModelPrimaryUpdate({ cfg, modelRaw, field: "model" });
9+
// 1. Read config and resolve the model reference
10+
const snapshot = await readConfigFileSnapshot();
11+
if (!snapshot.valid) {
12+
const issues = snapshot.issues.map((i) => `- ${i.path}: ${i.message}`).join("\n");
13+
throw new Error(`Invalid config at ${snapshot.path}\n${issues}`);
14+
}
15+
const cfg = snapshot.config;
16+
const resolved = resolveModelTarget({ raw: modelRaw, cfg });
17+
const key = `${resolved.provider}/${resolved.model}`;
18+
19+
// 2. Validate against catalog (skip when catalog is empty — initial setup)
20+
const catalog = await loadModelCatalog({ config: cfg });
21+
if (catalog.length > 0) {
22+
const catalogKeys = new Set(catalog.map((e) => modelKey(e.provider, e.id)));
23+
if (!catalogKeys.has(key)) {
24+
throw new Error(
25+
`Unknown model: ${key}\nModel not found in catalog. Run "openclaw models list" to see available models.`,
26+
);
27+
}
28+
}
29+
30+
// 3. Track whether this is a new entry
31+
const isNewEntry = !cfg.agents?.defaults?.models?.[key];
32+
33+
// 4. Update config (using upstream's helper for the actual mutation)
34+
const updated = await updateConfig((c) => {
35+
return applyDefaultModelPrimaryUpdate({ cfg: c, modelRaw, field: "model" });
836
});
937

38+
// 5. Warn and log
39+
if (isNewEntry) {
40+
runtime.log(
41+
`Warning: "${key}" had no entry in models config. Added with empty config (no provider routing).`,
42+
);
43+
}
1044
logConfigUpdated(runtime);
1145
runtime.log(`Default model: ${updated.agents?.defaults?.model?.primary ?? modelRaw}`);
1246
}

0 commit comments

Comments
 (0)