{"record":{"id":"758fcc5ff3b8fd53","repo":"plandex-ai/plandex","slug":"error-decoding-request-body","errorCode":null,"errorMessage":"Error decoding request body","messagePattern":"Error decoding request body","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"app/server/handlers/plan_config.go","lineNumber":78,"sourceCode":"\tif auth == nil {\n\t\treturn\n\t}\n\n\tvars := mux.Vars(r)\n\tplanId := vars[\"planId\"]\n\n\tlog.Println(\"planId: \", planId)\n\n\tplan := authorizePlan(w, planId, auth)\n\tif plan == nil {\n\t\treturn\n\t}\n\n\tvar req shared.UpdatePlanConfigRequest\n\terr := json.NewDecoder(r.Body).Decode(&req)\n\tif err != nil {\n\t\tlog.Println(\"Error decoding request body: \", err)\n\t\thttp.Error(w, \"Error decoding request body\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\terr = db.StorePlanConfig(planId, req.Config)\n\tif err != nil {\n\t\tlog.Println(\"Error storing plan config: \", err)\n\t\thttp.Error(w, \"Error storing plan config\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tlog.Println(\"UpdatePlanConfigHandler processed successfully\")\n}\n\nfunc GetDefaultPlanConfigHandler(w http.ResponseWriter, r *http.Request) {\n\tlog.Println(\"Received request for GetDefaultPlanConfigHandler\")\n\n\tauth := Authenticate(w, r, true)\n\tif auth == nil {","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/handlers/plan_config.go#L60-L96","documentation":"HTTP 400 error returned by UpdatePlanConfigHandler when json.NewDecoder fails to decode the request body into a shared.UpdatePlanConfigRequest. This is a client-side validation failure: the payload is not valid JSON or does not match the expected request shape. Plan authorization has already succeeded, so only the request body itself is at fault.","triggerScenarios":"POST/PUT update-plan-config with non-JSON body, trailing garbage after valid JSON, wrong field types (e.g. string where number expected), sending Content-Length > 0 with an empty body, or forgetting to close the JSON object.","commonSituations":"Client sending form-encoded data instead of JSON, missing Content-Type handling in scripts, older client versions posting a stale request schema, curl commands with quoting mistakes, proxy stripping the body.","solutions":["Send a valid JSON body matching shared.UpdatePlanConfigRequest, e.g. {\"config\": {...}}","Set header Content-Type: application/json","Validate the JSON with a linter or jq before sending","Check for field type mismatches against the latest shared model (client/server version skew)","Log the raw body on the server when this 400 occurs to see what was received"],"exampleFix":"// before (curl)\ncurl -d config={\"model\":\"gpt-4\"} ...\n// after\ncurl -H 'Content-Type: application/json' -d '{\"config\":{\"model\":\"gpt-4\"}}' ...","handlingStrategy":"validation","validationCode":"// client: validate body before sending\nconst body = { config: planConfig };\nconst json = JSON.stringify(body);\nif (!json || json === '{}') throw new Error('request body required');\nawait fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: json });","typeGuard":"function isUpdatePlanConfigRequest(v) {\n  return v != null && typeof v === 'object' && 'config' in v && typeof v.config === 'object';\n}","tryCatchPattern":"try {\n  await updatePlanConfig(planId, config);\n} catch (e) {\n  if (e.status === 400) { /* show user 'invalid JSON body'; do not retry */ }\n  else throw e;\n}","preventionTips":["Always send Content-Type: application/json","Validate payload shape client-side against the shared schema","Verify JSON with jq/JSON.parse before sending","Keep client and server shared models in sync"],"tags":["http-400","json","request-body"],"backgroundTag":"json-decode-failed","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}