fix(model): guide runtime allowlist repairs

This commit is contained in:
Vincent Koc
2026-05-04 15:14:11 -07:00
parent 70b1c17ae0
commit e091d912ce
6 changed files with 55 additions and 3 deletions

View File

@@ -119,7 +119,8 @@ openclaw config set agents.defaults.models '{"openai/gpt-5.4":{}}' --strict-json
If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and for session overrides. When a user selects a model that isn't in that allowlist, OpenClaw returns:
```
Model "provider/model" is not allowed. Use /model to list available models.
Model "provider/model" is not allowed. Use /models to list providers, or /models <provider> to list models.
Add it with: openclaw config set agents.defaults.models '{"provider/model":{}}' --strict-json --merge
```
<Warning>
@@ -131,6 +132,8 @@ This happens **before** a normal reply is generated, so the message can feel lik
</Warning>
When the rejected command included a runtime override such as `/model openai/gpt-5.5 --runtime codex`, fix the allowlist first, then retry the same `/model ... --runtime ...` command. For native Codex execution, the selected model is still `openai/gpt-5.5`; the `codex` runtime selects the harness and uses Codex auth separately.
For local/GGUF models, store the full provider-prefixed ref in the allowlist,
for example `ollama/gemma4:26b`, `lmstudio/Gemma4-26b-a4-it-gguf`, or the
exact provider/model shown by `openclaw models list --provider <provider>`.

View File

@@ -191,11 +191,14 @@ troubleshooting, see the main [FAQ](/help/faq).
session overrides. Choosing a model that isn't in that list returns:
```
Model "provider/model" is not allowed. Use /model to list available models.
Model "provider/model" is not allowed. Use /models to list providers, or /models <provider> to list models.
Add it with: openclaw config set agents.defaults.models '{"provider/model":{}}' --strict-json --merge
```
That error is returned **instead of** a normal reply. Fix: add the model to
`agents.defaults.models`, remove the allowlist, or pick a model from `/model list`.
If the command also included `--runtime codex`, add the model first and then retry
the same `/model provider/model --runtime codex` command.
</Accordion>

View File

@@ -80,6 +80,7 @@ export function resolveModelSelectionFromDirective(params: {
defaultModel: params.defaultModel,
aliasIndex: params.aliasIndex,
allowedModelKeys: params.allowedModelKeys,
rawRuntime: params.directives.rawModelRuntime,
})
: null;
const useStoredNumericProfile =
@@ -131,6 +132,7 @@ export function resolveModelSelectionFromDirective(params: {
defaultModel: params.defaultModel,
aliasIndex: params.aliasIndex,
allowedModelKeys: params.allowedModelKeys,
rawRuntime: params.directives.rawModelRuntime,
});
if (resolved.error) {

View File

@@ -611,6 +611,22 @@ describe("/model chat UX", () => {
expect(resolved.errorText).toContain("Browse: /models or /models <provider>");
});
it("includes additive allowlist repair when a runtime switch targets a blocked model", () => {
const resolved = resolveModelSelectionForCommand({
command: "/model openai/gpt-5.5 --runtime codex",
allowedModelKeys: new Set(["anthropic/claude-opus-4-6"]),
allowedModelCatalog: [],
});
expect(resolved.modelSelection).toBeUndefined();
expect(resolved.errorText).toContain('Model "openai/gpt-5.5" is not allowed.');
expect(resolved.errorText).toContain(
`openclaw config set agents.defaults.models '{"openai/gpt-5.5":{}}' --strict-json --merge`,
);
expect(resolved.errorText).toContain("Then retry: /model openai/gpt-5.5 --runtime codex");
expect(resolved.errorText).toContain("openclaw plugins enable codex");
});
it("treats explicit default /model selection as resettable default", () => {
const resolved = resolveModelSelectionForCommand({
command: "/model anthropic/claude-opus-4-6",

View File

@@ -265,6 +265,7 @@ export async function maybeHandleModelDirectiveInfo(params: {
"",
"Tap below to browse models, or use:",
"/model <provider/model> to switch",
"/model <provider/model> --runtime <runtime> to switch harnesses",
"/model status for details",
]
.filter(Boolean)

View File

@@ -20,6 +20,29 @@ export type ModelDirectiveSelection = {
alias?: string;
};
function formatAddModelCommand(modelRef: string): string {
return `openclaw config set agents.defaults.models '${JSON.stringify({ [modelRef]: {} })}' --strict-json --merge`;
}
function formatNotAllowedError(params: {
modelRef: string;
rawRuntime?: string | undefined;
}): string {
const rawRuntime = params.rawRuntime?.trim();
const retryCommand = rawRuntime
? `/model ${params.modelRef} --runtime ${rawRuntime}`
: `/model ${params.modelRef}`;
const lines = [
`Model "${params.modelRef}" is not allowed. Use /models to list providers, or /models <provider> to list models.`,
`Add it with: ${formatAddModelCommand(params.modelRef)}`,
`Then retry: ${retryCommand}`,
];
if (rawRuntime && normalizeProviderId(rawRuntime) === "codex") {
lines.push("If the Codex runtime is missing, run: openclaw plugins enable codex");
}
return lines.join("\n");
}
const FUZZY_VARIANT_TOKENS = [
"lightning",
"preview",
@@ -238,6 +261,7 @@ export function resolveModelDirectiveSelection(params: {
defaultModel: string;
aliasIndex: ModelAliasIndex;
allowedModelKeys: Set<string>;
rawRuntime?: string | undefined;
}): { selection?: ModelDirectiveSelection; error?: string } {
const { raw, defaultProvider, defaultModel, aliasIndex, allowedModelKeys } = params;
@@ -401,6 +425,9 @@ export function resolveModelDirectiveSelection(params: {
}
return {
error: `Model "${resolved.ref.provider}/${resolved.ref.model}" is not allowed. Use /models to list providers, or /models <provider> to list models.`,
error: formatNotAllowedError({
modelRef: `${resolved.ref.provider}/${resolved.ref.model}`,
rawRuntime: params.rawRuntime,
}),
};
}