sipeed/picoclaw · error

Model %q cannot be used as the default chat model

Error message

Model %q cannot be used as the default chat model

What it means

Returned by POST /api/models/default when the model is real and non-virtual but its provider fails defaultModelAllowedForModelConfig, which extracts the provider protocol and checks providers.IsDefaultModelProvider. Only chat-capable providers qualify; utility providers (embedding, TTS, ASR, etc.) are rejected because the default drives agent chat, and a non-chat model would break every new conversation.

Source

Thrown at web/backend/api/models.go:582

	for _, m := range cfg.ModelList {
		if m.ModelName == req.ModelName {
			found = true
			isVirtual = m.IsVirtual()
			break
		}
	}
	if !found {
		http.Error(w, fmt.Sprintf("Model %q not found in model_list", req.ModelName), http.StatusNotFound)
		return
	}
	if isVirtual {
		http.Error(w, fmt.Sprintf("Cannot set virtual model %q as default", req.ModelName), http.StatusBadRequest)
		return
	}
	for _, m := range cfg.ModelList {
		if m.ModelName == req.ModelName {
			if !defaultModelAllowedForModelConfig(m) {
				http.Error(
					w,
					fmt.Sprintf("Model %q cannot be used as the default chat model", req.ModelName),
					http.StatusBadRequest,
				)
				return
			}
			break
		}
	}

	cfg.Agents.Defaults.ModelName = req.ModelName

	if err := config.SaveConfig(h.configPath, cfg); err != nil {
		http.Error(w, fmt.Sprintf("Failed to save config: %v", err), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Choose a model backed by a chat-capable provider (e.g. an OpenAI/Anthropic-compatible LLM) as the default
  2. List models and pick one whose provider is in the default-eligible set exposed by the models endpoint
  3. If you believe the provider should qualify, check IsDefaultModelProvider in the providers package and upgrade or patch the allowlist

Example fix

// before
const target = models[0]; // e.g. an embedding model

// after
const target = models.find(m => m.provider === 'openai');
Defensive patterns

Strategy: validation

Validate before calling

const models = await (await fetch('/api/models')).json();
const chatProviders = new Set(['openai', 'anthropic', 'google-antigravity']); // chat-capable providers
const target = models.find((m: any) => chatProviders.has((m.provider || '').toLowerCase()));
if (!target) throw new Error('no default-eligible chat model configured');
await postDefault(target.model_name);

Type guard

function isDefaultAllowedProvider(provider: string): boolean {
  const chat = new Set(['openai', 'anthropic', 'google-antigravity']);
  return chat.has(provider.toLowerCase());
}

Try / catch

try {
  const res = await fetch('/api/models/default', {...});
  if (res.status === 400 && (await res.text()).includes('default chat model')) {
    /* restrict picker to chat-capable providers and retry */
  }
} catch (e) { /* network */ }

Prevention

When it happens

Trigger: POST /api/models/default naming a model whose provider is, for example, an embedding or speech provider rather than an LLM chat provider. The model exists and is not virtual, but ExtractProtocol yields a provider outside the default-model allowlist.

Common situations: The config mixes ASR/embedding models into model_list and a script picks the first entry; a user assumes any configured model can be the default; provider renaming moved a chat provider out of the allowlist after an upgrade.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/45d1cc0ba34aef72. Report an issue: GitHub.