{"record":{"id":"504460cdc211da4d","repo":"windmill-labs/windmill","slug":"http-agent-request-put-failed","errorCode":null,"errorMessage":"HTTP agent request PUT {} failed {}","messagePattern":"HTTP agent request PUT (.+?) failed (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-common/src/worker.rs","lineNumber":642,"sourceCode":"                url,\n                response.status()\n            ))\n        }\n    }\n\n    pub async fn put_bytes(&self, url: &str, bytes: Bytes) -> anyhow::Result<()> {\n        let base_url = self.base_internal_url.clone();\n        let response = self\n            .client\n            .put(format!(\"{}{}\", base_url, url))\n            .body(bytes)\n            .send()\n            .await\n            .map_err(|e| anyhow::anyhow!(e))?;\n        if response.status().is_success() {\n            Ok(())\n        } else {\n            Err(anyhow::anyhow!(\n                \"HTTP agent request PUT {} failed {}\",\n                url,\n                response.status()\n            ))\n        }\n    }\n}\n\n#[derive(Clone)]\npub enum Connection {\n    Sql(Pool<Postgres>),\n    Http(HttpClient),\n}\n\nimpl std::fmt::Debug for Connection {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Connection::Sql(_) => write!(f, \"Sql\"),","sourceCodeStart":624,"sourceCodeEnd":660,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-common/src/worker.rs#L624-L660","documentation":"Raised by HttpClient::put_bytes when a worker's HTTP-agent PUT request uploading raw bytes returns a non-2xx status. No response body is preserved, so the status code in the message is the only signal of why the upload was rejected.","triggerScenarios":"Worker calls HttpClient::put_bytes to upload file/blob content and the server returns 4xx (auth, quota, invalid path, payload too large) or 5xx (storage backend failure).","commonSituations":"Storage backend (S3/MinIO/filesystem) down or misconfigured; payload exceeding size limits (413); write-permission denied for the worker token (403); expired storage credentials server-side; wrong base_internal_url.","solutions":["Check server logs for the PUT request's error body (reason is not in the error message).","Verify storage backend health and configuration on the server (S3 creds, disk space).","Check the payload size against any upload limits and the token's write permissions.","Retry on 5xx with backoff; fix auth/size issues for 4xx."],"exampleFix":"// before\nclient.put_bytes(&url, bytes).await?;\n// after\nif let Err(e) = client.put_bytes(&url, bytes.clone()).await {\n    let msg = e.to_string();\n    if msg.contains(\"500\") || msg.contains(\"502\") || msg.contains(\"503\") {\n        tokio::time::sleep(Duration::from_secs(3)).await;\n        client.put_bytes(&url, bytes).await?;\n    } else {\n        return Err(e);\n    }\n}","handlingStrategy":"retry","validationCode":"// Check size and payload sanity before PUT\nif bytes.len() > MAX_UPLOAD_BYTES {\n    return Err(anyhow::anyhow!(\"payload {} exceeds upload limit\", bytes.len()));\n}\nif bytes.is_empty() {\n    return Err(anyhow::anyhow!(\"refusing empty PUT payload\"));\n}","typeGuard":null,"tryCatchPattern":"let mut attempt = 0;\nloop {\n    match client.put_bytes(&url, bytes.clone()).await {\n        Ok(()) => break,\n        Err(e) if attempt < 3 && (e.to_string().contains(\"500\") || e.to_string().contains(\"502\") || e.to_string().contains(\"503\") || e.to_string().contains(\"504\")) => {\n            attempt += 1;\n            tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;\n        }\n        Err(e) => return Err(anyhow::anyhow!(\"PUT {url} failed after {attempt} retries: {e}\")),\n    }\n}","preventionTips":["Validate payload size against server upload limits before PUT.","Monitor storage backend health (S3 creds, disk space) that serves the upload endpoint.","Confirm the worker token has write permissions on the target resource.","Use idempotent uploads so retries are safe.","Route retries only on 5xx; surface 4xx (403/413) as config errors immediately."],"tags":["http","network","worker","rust"],"backgroundTag":"http-non-2xx-response","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}