{"record":{"id":"ab971ca00ca96934","repo":"caddyserver/caddy","slug":"decoding-request-body-w","errorCode":null,"errorMessage":"decoding request body: %w","messagePattern":"decoding request body: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"admin.go","lineNumber":1172,"sourceCode":"// the operation at path according to method, using body and out as\n// needed. This is a low-level, unsynchronized function; most callers\n// will want to use changeConfig or readConfig instead. This requires a\n// read or write lock on currentCtxMu, depending on method (GET needs\n// only a read lock; all others need a write lock).\nfunc unsyncedConfigAccess(method, path string, body []byte, out io.Writer) error {\n\tvar err error\n\tvar val any\n\n\t// if there is a request body, decode it into the\n\t// variable that will be set in the config according\n\t// to method and path\n\tif len(body) > 0 {\n\t\terr = json.Unmarshal(body, &val)\n\t\tif err != nil {\n\t\t\tif jsonErr, ok := err.(*json.SyntaxError); ok {\n\t\t\t\treturn fmt.Errorf(\"decoding request body: %w, at offset %d\", jsonErr, jsonErr.Offset)\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"decoding request body: %w\", err)\n\t\t}\n\t}\n\n\tenc := json.NewEncoder(out)\n\n\tcleanPath := strings.Trim(path, \"/\")\n\tif cleanPath == \"\" {\n\t\treturn fmt.Errorf(\"no traversable path\")\n\t}\n\n\tparts := strings.Split(cleanPath, \"/\")\n\tif len(parts) == 0 {\n\t\treturn fmt.Errorf(\"path missing\")\n\t}\n\n\t// A path that ends with \"...\" implies:\n\t// 1) the part before it is an array\n\t// 2) the payload is an array","sourceCodeStart":1154,"sourceCodeEnd":1190,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/admin.go#L1154-L1190","documentation":"Returned by unsyncedConfigAccess when json.Unmarshal of the request body fails with an error that is not a *json.SyntaxError. Typical causes are unexpected EOF (truncated body) or 'invalid character ... after top-level value' (two JSON documents concatenated). The offset detail is omitted because the error type differs.","triggerScenarios":"Sending a body that ends mid-JSON (connection cut, Content-Length wrong), or sending two JSON values back to back like '{}{}', or a body that is valid UTF-8-invalid bytes caught by the generic unmarshal path.","commonSituations":"Client retries that append two payloads; proxy buffering bugs producing duplicated bodies; scripts piping partial file contents into curl; empty-but-nonzero bodies consisting of whitespace/newlines plus garbage.","solutions":["Ensure the body is exactly one complete JSON document; check Content-Length matches the bytes sent","Reproduce locally: printf '%s' \"$BODY\" | python3 -m json.tool to see the exact failure","If a proxy sits in front of the admin endpoint, bypass it or fix its buffering/rewrite rules","For truncated uploads, retry the request from scratch rather than resuming"],"exampleFix":"# before: two concatenated documents\ncurl -X PUT --data '{\"a\":1}{\"b\":2}' http://localhost:2019/config/x\n# after\ncurl -X PUT --data '{\"a\":1}' http://localhost:2019/config/x","handlingStrategy":"validation","validationCode":"printf '%s' \"$BODY\" | jq -e . >/dev/null && curl -X POST --data-binary \"$BODY\" http://localhost:2019/config/...","typeGuard":null,"tryCatchPattern":"Pre-validate with jq/python; if Caddy still 400s, suspect transport truncation (proxy, timeouts) and resend the identical body once over a direct connection.","preventionTips":["Use --data-binary, not -d with concatenated variables","Ensure exactly one top-level JSON value per request body"],"tags":["caddy","admin-api","json","bad-request","config"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}