OtterMind/Chat2DB · error · RequestError

Content-Length is required

Error message

Content-Length is required

What it means

Raised by RelayHandler._read_payload (relay_server.py:206) as a RequestError(HTTP 411) when the Content-Length header is missing or not parseable as an integer. The relay reads exactly Content-Length bytes, so a missing/unparseable value is rejected. A chunked-transfer request without a Content-Length also hits this.

Source

Thrown at script/github/qq_relay/relay_server.py:206

        self.send_header("Cache-Control", "no-store")
        self.send_header("X-Content-Type-Options", "nosniff")
        self.end_headers()
        self.wfile.write(body)

    def _authorize(self) -> None:
        expected = f"Bearer {self.relay_state.config.relay_token}"
        supplied = self.headers.get("Authorization", "")
        if not hmac.compare_digest(supplied, expected):
            raise RequestError(HTTPStatus.UNAUTHORIZED, "unauthorized")

    def _read_payload(self) -> Mapping[str, Any]:
        content_type = self.headers.get("Content-Type", "")
        if not content_type.lower().startswith("application/json"):
            raise RequestError(HTTPStatus.UNSUPPORTED_MEDIA_TYPE, "Content-Type must be JSON")
        try:
            content_length = int(self.headers.get("Content-Length", ""))
        except ValueError as error:
            raise RequestError(HTTPStatus.LENGTH_REQUIRED, "Content-Length is required") from error
        if content_length < 1 or content_length > MAX_REQUEST_BYTES:
            raise RequestError(HTTPStatus.REQUEST_ENTITY_TOO_LARGE, "request body is too large")
        try:
            payload = json.loads(self.rfile.read(content_length).decode("utf-8"))
        except (UnicodeDecodeError, json.JSONDecodeError) as error:
            raise RequestError(HTTPStatus.BAD_REQUEST, "request body is not valid JSON") from error
        if not isinstance(payload, Mapping):
            raise RequestError(HTTPStatus.BAD_REQUEST, "request body must be a JSON object")
        return payload

    def _validate_payload(self, payload: Mapping[str, Any]) -> tuple[str, str]:
        config = self.relay_state.config
        if payload.get("repository") != config.repository:
            raise RequestError(HTTPStatus.FORBIDDEN, "repository is not allowed")
        delivery_id = payload.get("delivery_id")
        if not isinstance(delivery_id, str) or not DELIVERY_ID_PATTERN.fullmatch(delivery_id):
            raise RequestError(HTTPStatus.BAD_REQUEST, "delivery_id is invalid")
        message = payload.get("message")

View on GitHub (pinned to 5ee1e990e7)

Solutions

  1. Send a valid integer Content-Length matching the body size.
  2. Avoid chunked transfer; send a fixed-length body so the client sets Content-Length.
  3. If a proxy is involved, ensure it forwards Content-Length unchanged.

Example fix

curl -X POST https://relay.example.com/v1/qq/github \
  -H "Authorization: Bearer $RELAY_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Content-Length: 123" \
  --data-binary @body.json
Defensive patterns

Strategy: validation

Validate before calling

try:
    content_length = int(headers.get("Content-Length", ""))
except ValueError:
    headers["Content-Length"] = str(len(body))

Type guard

def has_valid_content_length(headers: Mapping[str, str]) -> bool:
    try:
        return int(headers.get("Content-Length", "")) >= 1
    except ValueError:
        return False

Prevention

When it happens

Trigger: A POST with no Content-Length header, or a Content-Length value that is non-numeric. The bundled notifier (urllib) always sets Content-Length, so this is usually a manual client, a chunked client, or a proxy that removed the header.

Common situations: curl with chunked transfer encoding; a client using Transfer-Encoding: chunked without Content-Length; a proxy stripping Content-Length; a hand-crafted request omitting the header.

Related errors


AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14). Data as JSON: /api/errors/7a49259ca1398446. Report an issue: GitHub.