{"record":{"id":"3a689cbf341464a5","repo":"sipeed/picoclaw","slug":"invalid-index","errorCode":null,"errorMessage":"Invalid index","messagePattern":"Invalid index","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"web/backend/api/models.go","lineNumber":368,"sourceCode":"\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(map[string]any{\n\t\t\"status\": \"ok\",\n\t\t\"index\":  len(cfg.ModelList) - 1,\n\t})\n}\n\n// handleUpdateModel replaces a model configuration entry at the given index.\n// If the request body omits api_key (or sends an empty string), the existing\n// stored key is preserved so callers can update only api_base / proxy without\n// exposing or clearing the secret.\n//\n//\tPUT /api/models/{index}\nfunc (h *Handler) handleUpdateModel(w http.ResponseWriter, r *http.Request) {\n\tidx, err := strconv.Atoi(r.PathValue(\"index\"))\n\tif err != nil {\n\t\thttp.Error(w, \"Invalid index\", http.StatusBadRequest)\n\t\treturn\n\t}\n\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 rawFields map[string]json.RawMessage\n\tif err = json.Unmarshal(body, &rawFields); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Invalid JSON: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\ttype custom struct {\n\t\tconfig.ModelConfig","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/models.go#L350-L386","documentation":"HTTP 400 returned by PUT /api/models/{index} (handleUpdateModel) when strconv.Atoi(r.PathValue(\"index\")) fails — the path segment is not a plain base-10 integer. Note the range check happens later: a negative-but-numeric index like -1 parses fine here and instead produces the 404 'Index out of range' error.","triggerScenarios":"PUT /api/models/abc, /api/models/1.5, /api/models/%20, or /api/models/ (empty index from a trailing-slash route), all of which make Atoi return an error.","commonSituations":"Frontend building the URL with an undefined/null id (becomes 'undefined'); passing a model_name instead of the numeric list position; route typos or extra path segments; URL-encoding artifacts.","solutions":["Use the numeric position from GET /api/models (the index field of each entry), not the model name","Verify the value is a non-negative integer before building the URL: Number.isInteger(idx) && idx >= 0","Template it directly: `/api/models/${idx}` with idx from the list response — never string-concatenate an untrusted variable","Check for trailing slashes or empty segments in the request path"],"exampleFix":"// before: model name or undefined leaks into the URL\nawait fetch(`/api/models/${selected?.name}`);\n\n// after: validated numeric index from the listing\nif (!Number.isInteger(selected?.index) || selected.index < 0) throw new Error('bad index');\nawait fetch(`/api/models/${selected.index}`, { method: 'PUT', ... });","handlingStrategy":"validation","validationCode":"if (!Number.isInteger(idx) || idx < 0) throw new Error(`index must be a non-negative integer, got ${idx}`);","typeGuard":"function isModelIndex(v) {\n  return Number.isInteger(v) && v >= 0;\n}","tryCatchPattern":null,"preventionTips":["Always template the numeric index into the URL: `/api/models/${idx}`","Never substitute model_name or undefined into the {index} path slot","Keep a single source of truth: the index returned by GET /api/models","Assert the index type right before the fetch call in edit handlers"],"tags":["go","http","routing","validation","bad-request"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}