{"record":{"id":"3a9297bf76ea0aea","repo":"sipeed/picoclaw","slug":"id-is-required","errorCode":null,"errorMessage":"id is required","messagePattern":"id is required","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"web/backend/api/model_catalog.go","lineNumber":148,"sourceCode":"\tentries := make([]*CatalogEntry, 0, len(store.Entries))\n\tfor _, e := range store.Entries {\n\t\tentries = append(entries, e)\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(map[string]any{\n\t\t\"entries\": entries,\n\t\t\"total\":   len(entries),\n\t})\n}\n\n// handleDeleteCatalog deletes a saved model catalog by ID.\n//\n//\tDELETE /api/models/catalog/{id}\nfunc (h *Handler) handleDeleteCatalog(w http.ResponseWriter, r *http.Request) {\n\tid := r.PathValue(\"id\")\n\tif id == \"\" {\n\t\thttp.Error(w, \"id is required\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tstore, err := loadCatalogs()\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to load catalogs: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tif _, ok := store.Entries[id]; !ok {\n\t\thttp.Error(w, \"catalog not found\", http.StatusNotFound)\n\t\treturn\n\t}\n\n\tdelete(store.Entries, id)\n\tif err := saveCatalogs(store); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to save catalogs: %v\", err), http.StatusInternalServerError)\n\t\treturn","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/model_catalog.go#L130-L166","documentation":"Returned by DELETE /api/models/catalog/{id} when r.PathValue(\"id\") is empty. The route is registered as \"DELETE /api/models/catalog/{id}\" (Go 1.22 ServeMux), which matches /api/models/catalog/ with an empty segment - so in practice this error means the client built the URL with an empty id, typically fetch('/api/models/catalog/' + id) with id undefined or ''.","triggerScenarios":"Template-literal fetch with an undefined variable: `/api/models/catalog/${id}` when id is undefined renders as the bare catalog/ path; a raw DELETE to /api/models/catalog/; string concatenation producing a trailing slash.","commonSituations":"UI delete buttons firing before the row's id loaded; refactoring that renames the id variable; stale client code after the API changed shape.","solutions":["Guard the id client-side and skip the request when it is falsy","Always use an id obtained from GET /api/models/catalog (entries carry an \"id\" field)","If you see this in logs, log the failing URL to find which code path built it"],"exampleFix":"// before - id may be undefined, URL becomes /api/models/catalog/\nfetch(`/api/models/catalog/${id}`, {method: 'DELETE'})\n\n// after - guard before building the URL\nif (!id) throw new Error('catalog id is required');\nfetch(`/api/models/catalog/${encodeURIComponent(id)}`, {method: 'DELETE'})","handlingStrategy":"validation","validationCode":"function assertCatalogId(id: unknown): asserts id is string {\n  if (typeof id !== 'string' || id.trim() === '') {\n    throw new Error('catalog id is required');\n  }\n}","typeGuard":"const isCatalogId = (v: unknown): v is string =>\n  typeof v === 'string' && v.trim() !== '';","tryCatchPattern":"if (!isCatalogId(row?.id)) return;   // guard: never send DELETE with an empty id\nawait fetch(`/api/models/catalog/${encodeURIComponent(row.id)}`, {method: 'DELETE'});","preventionTips":["Guard falsy ids before building the URL - undefined renders as the bare catalog/ path","Always encodeURIComponent the id - it contains '|' separators","Source ids only from GET /api/models/catalog entries"],"tags":["validation","routing","http","model-catalog","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}