{"record":{"id":"4fd2b33d34962784","repo":"risingwavelabs/risingwave","slug":"can-t-clone-request","errorCode":null,"errorMessage":"Can't clone request","messagePattern":"Can't clone request","errorType":"exception","errorClass":"SinkError","httpStatus":null,"severity":"error","filePath":"src/connector/src/sink/doris_starrocks_connector.rs","lineNumber":418,"sourceCode":"/// The reason we handle the redirection manually is that if we let `reqwest` handle the redirection\n/// automatically, it will remove sensitive headers (such as Authorization) during the redirection,\n/// and there's no way to prevent this behavior.\n/// Please note, the FE address that user specified might be a FE follower not the leader, in this case,\n/// the follower FE will redirect request to leader FE and then to BE.\nasync fn send_stream_load_request(\n    client: Client,\n    mut request: Request,\n    fe_host: &str,\n) -> Result<StreamLoadResponse> {\n    // possible redirection paths:\n    // RW <-> follower FE -> leader FE -> BE\n    // RW <-> leader FE -> BE\n    // RW <-> leader FE\n    for _ in 0..2 {\n        let original_http_port = request.url().port();\n        let mut request_for_redirection = request\n            .try_clone()\n            .ok_or_else(|| SinkError::DorisStarrocksConnect(anyhow!(\"Can't clone request\")))?;\n        let resp = client.execute(request).await.map_err(|err| {\n            SinkError::DorisStarrocksConnect(\n                anyhow!(err).context(\"sending stream load request failed\"),\n            )\n        })?;\n        let be_url = try_get_be_url(&resp, fe_host)?;\n        match be_url {\n            Some(be_url) => {\n                // we used an unconventional method to detect if we are currently redirecting to FE leader, i.e.,\n                // by comparing the port of the redirected url with that of the original request, if they are same, we consider\n                // this is a FE address. Because in practice, no one would deploy their `StarRocks` cluster with the same\n                // http port for both FE and BE. However, this is a potentially problematic assumption,\n                // we may investigate a better way to do this. For example, we could use the `show backends` command to check\n                // if the host of the redirected url is in the list. However, `show backends` requires\n                // the system-level privilege, which could break the backward compatibility.\n                let redirected_port = be_url.port();\n                *request_for_redirection.url_mut() = be_url;\n                if redirected_port == original_http_port {","sourceCodeStart":400,"sourceCodeEnd":436,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/connector/src/sink/doris_starrocks_connector.rs#L400-L436","documentation":"`send_stream_load_request` manually follows FE redirects (to preserve Authorization headers that reqwest would strip) by cloning the outgoing request each round with `try_clone`. `Request::try_clone` returns None when the request body is not cloneable (a non-in-memory/streaming body), so cloning fails and this error is thrown.","triggerScenarios":"`send_stream_load_request` is called (from `send` or `build_txn_inserter`) with a request whose body cannot be cloned — e.g. a streaming body rather than a fully-buffered `Bytes` body — on the first or redirect iteration.","commonSituations":"Upstream code change replaced a `Bytes` body with a stream; regression after refactor of how the txn/stream-load request body is constructed; attempting to reuse a request that already had its body consumed.","solutions":["Ensure the request passed to `send_stream_load_request` has an in-memory body (`Bytes`/`&'static [u8]`), which reqwest can clone","Refactor so the body is rebuilt from a buffer instead of relying on `try_clone` on a streaming body","Check recent changes to request construction in `send`/`build_txn_inserter`; this is typically a code bug, not a user config issue","As a workaround, reconstruct the request per redirect round using stored URL/headers/body instead of cloning"],"exampleFix":"// before: body built as a stream, try_clone returns None\nlet request = client.put(url).body(Body::wrap_stream(stream));\n// after: body buffered so the request is clonable\nlet request = client.put(url).body(bytes); // Bytes: try_clone succeeds","handlingStrategy":"validation","validationCode":"// ensure the request body is clonable before calling the send path\nassert!(matches!(body, reqwest::Body::Bytes(_) | reqwest::Body::Wrapper(_)) == false || !matches!(body, reqwest::Body::Stream(_)), \"body must be buffered, not a stream\");","typeGuard":null,"tryCatchPattern":"let request = match request.try_clone() {\n    Some(cloned) => cloned,\n    None => return Err(rebuild_request_from_parts(&request)),\n};","preventionTips":["Always build stream-load request bodies from buffered Bytes so try_clone succeeds","Add a unit test covering FE-follower -> FE-leader -> BE redirection","Avoid wrapping request bodies in streams on this code path"],"tags":["sink","doris","http-redirect","internal","reqwest"],"backgroundTag":"internal-invariant-violation","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}