{"record":{"id":"6c825c75fd1e3ebf","repo":"sipeed/picoclaw","slug":"model-name-is-required-6c825c","errorCode":null,"errorMessage":"model_name is required","messagePattern":"model_name is required","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"web/backend/api/models.go","lineNumber":551,"sourceCode":"//\tPOST /api/models/default\nfunc (h *Handler) handleSetDefaultModel(w http.ResponseWriter, r *http.Request) {\n\tbody, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))\n\tif err != nil {\n\t\thttp.Error(w, \"Failed to read request body\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tdefer r.Body.Close()\n\n\tvar req struct {\n\t\tModelName string `json:\"model_name\"`\n\t}\n\tif err = json.Unmarshal(body, &req); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Invalid JSON: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif req.ModelName == \"\" {\n\t\thttp.Error(w, \"model_name is required\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tcfg, err := config.LoadConfig(h.configPath)\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to load config: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Verify the model_name exists in model_list and is not a virtual model\n\tfound := false\n\tisVirtual := false\n\tfor _, m := range cfg.ModelList {\n\t\tif m.ModelName == req.ModelName {\n\t\t\tfound = true\n\t\t\tisVirtual = m.IsVirtual()\n\t\t\tbreak\n\t\t}","sourceCodeStart":533,"sourceCodeEnd":569,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/models.go#L533-L569","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Send {\"model_name\": \"<name>\"} with Content-Type: application/json and verify the key is exactly model_name","Check the client object for undefined/null before serializing (e.g. if (!name) throw)","GET /api/models first and populate the picker from real model_name values so an empty submit is impossible"],"exampleFix":"// before\nawait fetch('/api/models/default', {\n  method: 'POST',\n  headers: {'Content-Type': 'application/json'},\n  body: JSON.stringify({ model: selectedName }),\n});\n\n// after\nawait fetch('/api/models/default', {\n  method: 'POST',\n  headers: {'Content-Type': 'application/json'},\n  body: JSON.stringify({ model_name: selectedName }),\n});","handlingStrategy":"validation","validationCode":"function assertSetDefaultPayload(body: unknown): { model_name: string } {\n  if (typeof body !== 'object' || body === null) throw new Error('body must be an object');\n  const name = (body as any).model_name;\n  if (typeof name !== 'string' || name.trim() === '') throw new Error('model_name is required');\n  return { model_name: name };\n}\n// before sending:\nconst payload = assertSetDefaultPayload({ model_name: selected });\nawait fetch('/api/models/default', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(payload) });","typeGuard":"function isSetDefaultModelBody(v: unknown): v is { model_name: string } {\n  return typeof v === 'object' && v !== null\n    && typeof (v as any).model_name === 'string'\n    && (v as any).model_name.length > 0;\n}","tryCatchPattern":"try {\n  const res = await fetch('/api/models/default', {...});\n  if (res.status === 400) { /* show 'model_name is required' next to the picker */ }\n} catch (e) { /* network-level only */ }","preventionTips":["Bind the default-model picker to real model_name values from GET /api/models and disable submit while empty","Name the field model_name everywhere — add a lint/ESLint rule or shared type for the request payload","Unit-test the client payload shape against the API contract in CI"],"tags":["http","validation","json","api","default-model"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}