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
- Remove the body from non-POST requests — send the data as query parameters or headers for GET/DELETE.
- Switch the request to POST if the payload is intentional.
- 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
- Use .query() for GET/DELETE data instead of a JSON body.
- Audit HTTP client wrappers for unconditional .body()/.json() calls.
- Reserve request bodies for POST/PUT only as a team convention.
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
- remote cache blob pack has an invalid content type
- relay concurrency must be between 1 and 32
- unsupported GitHub operation
- unsupported query parameter
- unsupported redirect
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/3b1700475fd0f616.
Report an issue: GitHub.