jdx/mise · error

unexpected request body

Error message

unexpected request body

What it means

The relay forwards proxied GitHub requests upstream and caps request bodies at 8 MiB. It reads the full body first, then enforces the HTTP convention that only POST (and methods with bodies like PUT) may carry a payload. Any non-POST request that arrives with a non-empty body is rejected with "unexpected request body" instead of being forwarded.

Source

Thrown at src/github_relay.rs:616

                .body(Body::from("GitHub relay request denied or unavailable"))
                .expect("valid response"),
        }
    }

    async fn forward(broker: Broker, request: Request) -> Result<Response> {
        let deadline = tokio::time::Instant::now() + broker.audit.options.request_timeout;
        let operation = broker.audit.operation(&broker.scope, &request);
        let permit = broker.permits.clone().try_acquire_owned()?;
        let target = authorize(
            &broker.scope,
            request.method().as_str(),
            request.uri().path().strip_prefix('/').unwrap_or_default(),
            request.uri().query(),
        )?;
        let (parts, body) = request.into_parts();
        let body = to_bytes(body, 8 * 1024 * 1024).await?;
        if parts.method != Method::POST && !body.is_empty() {
            bail!("unexpected request body");
        }
        let upstream = target.url.clone();
        #[cfg(test)]
        let upstream = if let Some(base) = &broker.test_upstream {
            let url = Url::parse(&upstream)?;
            format!(
                "{base}{}{}",
                url.path(),
                url.query().map(|q| format!("?{q}")).unwrap_or_default()
            )
        } else {
            upstream
        };
        let mut req = broker.client.request(parts.method.clone(), upstream);
        if target.git {
            req = req.basic_auth("x-access-token", Some(broker.token.as_str()));
        } else {
            req = req.bearer_auth(broker.token.as_str());

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Remove the body from non-POST requests — send the data as query parameters or headers for GET/DELETE.
  2. Switch the request to POST if the payload is intentional.
  3. If a client library insists on a body, configure it to omit the body for bodyless methods (e.g. http2/reqwest builder without .body()).

Example fix

// before
client.get(url).json(&query).send().await?;
// after
client.get(url).query(&query).send().await?;
Defensive patterns

Strategy: validation

Validate before calling

if method != Method::POST && body_size > 0 {
    return Err("non-POST requests must not carry a body");
}

Try / catch

match result {
    Err(e) if e.to_string().contains("unexpected request body") =>
        retry_without_body(request),
    other => other,
}

Prevention

When it happens

Trigger: Sending GET/HEAD/DELETE (any method other than POST) through the relay's forward path while including a request body, e.g. an HTTP client that always sets a body or a JSON body attached to a GET call.

Common situations: HTTP client libraries auto-attaching an empty-but-nonzero body; middleware injecting bodies into GETs; hand-rolled requests where the developer used GET but passed a body parameter; proxies re-encoding requests with a body.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/3b1700475fd0f616. Report an issue: GitHub.