Management API answers 400 instead of 413 for an over-cap body without Content-Length
Summary
A management API request whose body exceeds server.max_body_size gets 413 request_entity_too_large when it declares a Content-Length, but 400 bad_request with "request body contains malformed JSON" when it does not (chunked transfer). The status depends on how the client framed the request, not on what it sent.
Where
BodySizeMiddleware enforces the cap in two places: the Content-Length pre-check and the MaxBytesReader wrap (middleware.go). Only the first writes a 413. For the second, the overflow surfaces inside the handler's decode, and the management decode path (request.go) has no *http.MaxBytesError branch, so it falls through to the generic malformed-JSON 400.
internal/gitlabapi does check the typed error (request.go), but that check is unreliable on its own: the decoder usually wraps the read error in its own *json.SyntaxError, and whether the type survives depends on the decoder's buffer state when the cap trips. Measured on go1.26.5 for one fixture, the typed error reaches the caller at caps 1-7 and 71 and not at 9, so the 413 there is a matter of byte position.
Reproduce
POST /api/v1/{slug}/repositories with a body larger than server.max_body_size and Transfer-Encoding: chunked (no Content-Length):
- expected:
413,request_entity_too_large - actual:
400,bad_request,"request body contains malformed JSON"
PATCH on a repository behaves the same way.
Impact
A client cannot tell "your body is too big, retry smaller" from "your JSON is broken", so an oversized request looks like a client-side serialization bug. api/openapi/v1.yaml also documents no 413 on any management route, even though the middleware already returns one.
Fix direction
Detect the tripped MaxBytesReader by probing the body (it replays *http.MaxBytesError on every Read after the limit) and share the classification between the two JSON API surfaces so they cannot drift again. Document the 413 on the routes that take a body.
Triage note (automated): This issue has been classified as
type::bug. The description clearly documents incorrect behaviour — the HTTP status code returned for an oversized chunked request body (400) differs from the correct and expected status (413), depending solely on how the client frames the request rather than on what it actually sent. This is a functional defect in the server's error-handling path, not a feature request or maintenance task.Labels applied:
type::bug,Category:Artifact Registry,devops::package,group::package registry.If this classification doesn't look right to you, please update the type label and let the team know — happy to be corrected!