{"record":{"id":"528a285dee5b5528","repo":"sipeed/picoclaw","slug":"invalid-json","errorCode":null,"errorMessage":"Invalid JSON","messagePattern":"Invalid JSON","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"web/backend/api/models.go","lineNumber":997,"sourceCode":"//\n//\tPOST /api/models/test-inline\nfunc (h *Handler) handleTestInlineModel(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\n\tvar req struct {\n\t\tProvider   string `json:\"provider\"`\n\t\tModel      string `json:\"model\"`\n\t\tAPIBase    string `json:\"api_base\"`\n\t\tAPIKey     string `json:\"api_key\"`\n\t\tAuthMethod string `json:\"auth_method\"`\n\t\tModelIndex *int   `json:\"model_index\"`\n\t}\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\thttp.Error(w, \"Invalid JSON\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tm := &config.ModelConfig{\n\t\tProvider:   strings.TrimSpace(req.Provider),\n\t\tModel:      strings.TrimSpace(req.Model),\n\t\tAPIBase:    strings.TrimSpace(req.APIBase),\n\t\tAuthMethod: strings.TrimSpace(req.AuthMethod),\n\t}\n\tif req.APIKey != \"\" {\n\t\tm.SetAPIKey(req.APIKey)\n\t}\n\n\t// When api_key is empty and model_index is provided, fall back to stored credentials.\n\t// This lets the edit form test unsaved field changes while using the saved key.\n\t// Only reuse the stored key when the provider and effective API base match\n\t// the saved model, to prevent attaching a credential to a different endpoint.\n\tif req.APIKey == \"\" && req.ModelIndex != nil {","sourceCodeStart":979,"sourceCodeEnd":1015,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/models.go#L979-L1015","documentation":"Returned by POST /api/models/test-inline when json.Unmarshal of the body into {provider, model, api_base, api_key, auth_method, model_index} fails. Unlike sibling handlers, this error message omits the %v detail, so you must validate the JSON client-side to find the cause. Expected shape: one JSON object; model_index must be a number when present.","triggerScenarios":"POST /api/models/test-inline with unquoted keys, an array body, {\"model_index\": \"2\"} (string vs *int), double-stringified JSON, or trailing garbage after the object.","commonSituations":"Reusing a fetch wrapper that JSON.stringify's an already-serialized string; sending FormData without conversion; template-built JSON with a trailing comma; field renamed on the client (model_name vs model).","solutions":["Validate with JSON.parse on the client before sending — the server message has no detail to help you","Send exactly: {provider, model, api_base, api_key, auth_method, model_index} with model_index as a number","Ensure Content-Type: application/json and a single JSON.stringify call"],"exampleFix":"// before\nbody: rawJsonString // previously built via template literal\n\n// after\nbody: JSON.stringify({\n  provider: 'openai',\n  model: 'gpt-4o',\n  api_base: 'https://api.openai.com/v1',\n  api_key: key,\n  model_index: 0,\n})","handlingStrategy":"validation","validationCode":"function buildTestInlineBody(p: { provider: string; model: string; api_base?: string; api_key?: string; auth_method?: string; model_index?: number }): string {\n  if (typeof p.provider !== 'string' || typeof p.model !== 'string') throw new Error('provider and model are required strings');\n  if (p.model_index !== undefined && !Number.isInteger(p.model_index)) throw new Error('model_index must be an integer');\n  return JSON.stringify(p);\n}","typeGuard":"function isTestInlinePayload(v: unknown): v is { provider: string; model: string; api_base?: string; api_key?: string; auth_method?: string; model_index?: number } {\n  if (typeof v !== 'object' || v === null) return false;\n  const o = v as Record<string, unknown>;\n  return typeof o.provider === 'string' && typeof o.model === 'string'\n    && (o.model_index === undefined || typeof o.model_index === 'number');\n}","tryCatchPattern":"try {\n  const res = await fetch('/api/models/test-inline', {...});\n  if (res.status === 400 && (await res.text()) === 'Invalid JSON') {\n    /* server gives no detail — run JSON.parse(body) locally to locate the defect */\n  }\n} catch (e) { /* network */ }","preventionTips":["Since this endpoint omits error detail, validate with JSON.parse round-trip on the client","Use the exact field names: provider, model, api_base, api_key, auth_method, model_index","model_index must be a JSON number when present"],"tags":["http","json","validation","api"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}