diff --git a/crates/s3-client/src/api_list.rs b/crates/s3-client/src/api_list.rs
index c2771d081..dab74cf84 100644
--- a/crates/s3-client/src/api_list.rs
+++ b/crates/s3-client/src/api_list.rs
@@ -471,4 +471,58 @@ mod tests {
assert_eq!(parsed.delete_markers.len(), 1);
assert_eq!(parsed.delete_markers[0].version_id, "marker-a");
}
+
+ // Regression test for backlog#2076: a ListObjectsV2 response for a delimited
+ // listing over a bucket that holds nested-key objects (e.g. any warm-tier
+ // target that already stores more than one flat object) includes a
+ // ... element. `CommonPrefix`
+ // previously had no `rename_all = "PascalCase"`, so quick_xml looked for a
+ // lowercase `` child, never found one, and (with no `#[serde(default)]`
+ // either) failed the whole response with "missing field `prefix`" — surfacing to
+ // callers of `WarmBackendS3::in_use()` (tier add/remove) as `TierPermErr`.
+ #[test]
+ fn list_objects_v2_xml_parses_common_prefixes() {
+ let xml = br#"
+
+ warm-bucket
+
+ /
+ 1
+ false
+
+ subdir/
+
+
+ "#;
+
+ let parsed = quick_xml::de::from_reader::<_, ListBucketV2Result>(xml.as_slice()).expect("ListObjectsV2 XML should parse");
+
+ assert_eq!(parsed.common_prefixes.len(), 1);
+ assert_eq!(parsed.common_prefixes[0].prefix, "subdir/");
+ }
+
+ // Same fixture shape as list_object_versions_query hits (ListVersionsResult
+ // reuses the same CommonPrefix type).
+ #[test]
+ fn list_object_versions_xml_parses_common_prefixes() {
+ let xml = br#"
+
+ warm-bucket
+
+
+
+ 1
+ false
+
+ subdir/
+
+
+ "#;
+
+ let parsed =
+ quick_xml::de::from_reader::<_, ListVersionsResult>(xml.as_slice()).expect("ListObjectVersions XML should parse");
+
+ assert_eq!(parsed.common_prefixes.len(), 1);
+ assert_eq!(parsed.common_prefixes[0].prefix, "subdir/");
+ }
}
diff --git a/crates/s3-client/src/api_s3_datatypes.rs b/crates/s3-client/src/api_s3_datatypes.rs
index d17eb6cbb..d7797e96b 100644
--- a/crates/s3-client/src/api_s3_datatypes.rs
+++ b/crates/s3-client/src/api_s3_datatypes.rs
@@ -30,6 +30,7 @@ use crate::utils::base64_decode;
use super::transition_api;
#[derive(Debug, Default, Serialize, Deserialize)]
+#[serde(default, rename_all = "PascalCase")]
pub struct CommonPrefix {
pub prefix: String,
}