caddyserver/caddy · error

decoding request body: %w

Error message

decoding request body: %w

What it means

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.

Source

Thrown at admin.go:1172

// the operation at path according to method, using body and out as
// needed. This is a low-level, unsynchronized function; most callers
// will want to use changeConfig or readConfig instead. This requires a
// read or write lock on currentCtxMu, depending on method (GET needs
// only a read lock; all others need a write lock).
func unsyncedConfigAccess(method, path string, body []byte, out io.Writer) error {
	var err error
	var val any

	// if there is a request body, decode it into the
	// variable that will be set in the config according
	// to method and path
	if len(body) > 0 {
		err = json.Unmarshal(body, &val)
		if err != nil {
			if jsonErr, ok := err.(*json.SyntaxError); ok {
				return fmt.Errorf("decoding request body: %w, at offset %d", jsonErr, jsonErr.Offset)
			}
			return fmt.Errorf("decoding request body: %w", err)
		}
	}

	enc := json.NewEncoder(out)

	cleanPath := strings.Trim(path, "/")
	if cleanPath == "" {
		return fmt.Errorf("no traversable path")
	}

	parts := strings.Split(cleanPath, "/")
	if len(parts) == 0 {
		return fmt.Errorf("path missing")
	}

	// A path that ends with "..." implies:
	// 1) the part before it is an array
	// 2) the payload is an array

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Ensure the body is exactly one complete JSON document; check Content-Length matches the bytes sent
  2. Reproduce locally: printf '%s' "$BODY" | python3 -m json.tool to see the exact failure
  3. If a proxy sits in front of the admin endpoint, bypass it or fix its buffering/rewrite rules
  4. For truncated uploads, retry the request from scratch rather than resuming

Example fix

# before: two concatenated documents
curl -X PUT --data '{"a":1}{"b":2}' http://localhost:2019/config/x
# after
curl -X PUT --data '{"a":1}' http://localhost:2019/config/x
Defensive patterns

Strategy: validation

Validate before calling

printf '%s' "$BODY" | jq -e . >/dev/null && curl -X POST --data-binary "$BODY" http://localhost:2019/config/...

Try / catch

Pre-validate with jq/python; if Caddy still 400s, suspect transport truncation (proxy, timeouts) and resend the identical body once over a direct connection.

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/ab971ca00ca96934. Report an issue: GitHub.