From e1fee02c9a31e640aaec3d87304a5963e86ab261 Mon Sep 17 00:00:00 2001 From: Adrian Wilkins-Caruana Date: Thu, 10 Sep 2026 06:21:47 +0000 Subject: [PATCH] Retry 409 Conflict on conditional PutMode::Create in S3 The If-Match (Update) branch already retries on 409. The If-None-Match (Create) branch did not. AWS documents a 409 for If-None-Match when a concurrent delete of the key completes before the write does, and states that PutObject uploads may be retried after a 409. Concurrent conditional creates on the same key instead return 412, which this branch already maps to AlreadyExists. A 409 means the request reached S3 and nothing was created, so the retry cannot turn a successful create into a reported conflict. This does not set `.idempotent(true)`, so transport-level retry behavior is unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- src/aws/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/aws/mod.rs b/src/aws/mod.rs index 8fcb4407..2b1d716e 100644 --- a/src/aws/mod.rs +++ b/src/aws/mod.rs @@ -258,7 +258,15 @@ impl ObjectStore for AmazonS3 { implementer: self.to_string(), }), (PutMode::Create, S3ConditionalPut::ETagMatch) => { - match request.header(&IF_NONE_MATCH, "*").do_put().await { + match request + .header(&IF_NONE_MATCH, "*") + // Real S3 can report 409 Conflict when a concurrent delete + // of the key completes before this write does. Nothing was + // written in that case, so the request is safe to retry. + .retry_on_conflict(true) + .do_put() + .await + { // Technically If-None-Match should return NotModified but some stores, // such as R2, instead return PreconditionFailed // https://developers.cloudflare.com/r2/api/s3/extensions/#conditional-operations-in-putobject