sipeed/picoclaw · error

model_name is required

Error message

model_name is required

What it means

Returned by POST /api/models/default (handleSetDefaultModel) when the JSON body parses but contains no non-empty model_name field. The handler unmarshals into an anonymous struct with a single ModelName field; any body that leaves it as the zero value (""), including an empty object or a body with a wrong key, is rejected with 400 before any config is loaded. This is a request-shape guard, not a model-existence check.

Source

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

//	POST /api/models/default
func (h *Handler) handleSetDefaultModel(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
	if err != nil {
		http.Error(w, "Failed to read request body", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()

	var req struct {
		ModelName string `json:"model_name"`
	}
	if err = json.Unmarshal(body, &req); err != nil {
		http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
		return
	}

	if req.ModelName == "" {
		http.Error(w, "model_name is required", http.StatusBadRequest)
		return
	}

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

	// Verify the model_name exists in model_list and is not a virtual model
	found := false
	isVirtual := false
	for _, m := range cfg.ModelList {
		if m.ModelName == req.ModelName {
			found = true
			isVirtual = m.IsVirtual()
			break
		}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Send {"model_name": "<name>"} with Content-Type: application/json and verify the key is exactly model_name
  2. Check the client object for undefined/null before serializing (e.g. if (!name) throw)
  3. GET /api/models first and populate the picker from real model_name values so an empty submit is impossible

Example fix

// before
await fetch('/api/models/default', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({ model: selectedName }),
});

// after
await fetch('/api/models/default', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({ model_name: selectedName }),
});
Defensive patterns

Strategy: validation

Validate before calling

function assertSetDefaultPayload(body: unknown): { model_name: string } {
  if (typeof body !== 'object' || body === null) throw new Error('body must be an object');
  const name = (body as any).model_name;
  if (typeof name !== 'string' || name.trim() === '') throw new Error('model_name is required');
  return { model_name: name };
}
// before sending:
const payload = assertSetDefaultPayload({ model_name: selected });
await fetch('/api/models/default', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload) });

Type guard

function isSetDefaultModelBody(v: unknown): v is { model_name: string } {
  return typeof v === 'object' && v !== null
    && typeof (v as any).model_name === 'string'
    && (v as any).model_name.length > 0;
}

Try / catch

try {
  const res = await fetch('/api/models/default', {...});
  if (res.status === 400) { /* show 'model_name is required' next to the picker */ }
} catch (e) { /* network-level only */ }

Prevention

When it happens

Trigger: POST /api/models/default with body {}, {"model_name": ""}, or {"model": "gpt-4o"} (wrong field name). Also hit when a client omits Content-Type: application/json and sends a form-encoded body the JSON decoder maps to nothing.

Common situations: Frontend sends a stale field name after an API rename (model vs model_name); a dropdown bound to an empty selection submits an empty string; JSON.stringify of an object whose property was undefined; curl calls missing the -d body entirely.

Related errors


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