{"record":{"id":"3b1700475fd0f616","repo":"jdx/mise","slug":"unexpected-request-body","errorCode":null,"errorMessage":"unexpected request body","messagePattern":"unexpected request body","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/github_relay.rs","lineNumber":616,"sourceCode":"                .body(Body::from(\"GitHub relay request denied or unavailable\"))\n                .expect(\"valid response\"),\n        }\n    }\n\n    async fn forward(broker: Broker, request: Request) -> Result<Response> {\n        let deadline = tokio::time::Instant::now() + broker.audit.options.request_timeout;\n        let operation = broker.audit.operation(&broker.scope, &request);\n        let permit = broker.permits.clone().try_acquire_owned()?;\n        let target = authorize(\n            &broker.scope,\n            request.method().as_str(),\n            request.uri().path().strip_prefix('/').unwrap_or_default(),\n            request.uri().query(),\n        )?;\n        let (parts, body) = request.into_parts();\n        let body = to_bytes(body, 8 * 1024 * 1024).await?;\n        if parts.method != Method::POST && !body.is_empty() {\n            bail!(\"unexpected request body\");\n        }\n        let upstream = target.url.clone();\n        #[cfg(test)]\n        let upstream = if let Some(base) = &broker.test_upstream {\n            let url = Url::parse(&upstream)?;\n            format!(\n                \"{base}{}{}\",\n                url.path(),\n                url.query().map(|q| format!(\"?{q}\")).unwrap_or_default()\n            )\n        } else {\n            upstream\n        };\n        let mut req = broker.client.request(parts.method.clone(), upstream);\n        if target.git {\n            req = req.basic_auth(\"x-access-token\", Some(broker.token.as_str()));\n        } else {\n            req = req.bearer_auth(broker.token.as_str());","sourceCodeStart":598,"sourceCodeEnd":634,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/github_relay.rs#L598-L634","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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())."],"exampleFix":"// before\nclient.get(url).json(&query).send().await?;\n// after\nclient.get(url).query(&query).send().await?;","handlingStrategy":"validation","validationCode":"if method != Method::POST && body_size > 0 {\n    return Err(\"non-POST requests must not carry a body\");\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"unexpected request body\") =>\n        retry_without_body(request),\n    other => other,\n}","preventionTips":["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."],"tags":["http","validation","request-body","relay"],"backgroundTag":"invalid-argument-value","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}