From f0e2de18f39aff3cc0d9fca62e59b6c7705e82dd Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Wed, 15 Apr 2026 12:55:29 -0700 Subject: [PATCH 1/2] fix(snapshots): Preserve subdirectory structure in manifest keys Use full relative paths (e.g. settings/profile.png) instead of bare filenames (e.g. profile.png) as manifest keys when uploading build snapshots. Previously, images in different subdirectories with the same filename would collide and only the first would be uploaded. Co-Authored-By: Claude --- src/commands/build/snapshots.rs | 37 +++++++-------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/src/commands/build/snapshots.rs b/src/commands/build/snapshots.rs index daf49772ee..8f6b9bb1a2 100644 --- a/src/commands/build/snapshots.rs +++ b/src/commands/build/snapshots.rs @@ -354,8 +354,7 @@ fn upload_images( .context("Failed to create tokio runtime")?; let mut manifest_entries = HashMap::new(); - let mut collisions: HashMap> = HashMap::new(); - let mut kept_paths = HashMap::new(); + let mut duplicates: Vec = Vec::new(); let mut uploads = Vec::with_capacity(images.len()); let hashed_images: Vec<_> = images @@ -367,20 +366,10 @@ fn upload_images( .collect::>>()?; for (image, hash) in hashed_images { - let image_file_name = image - .relative_path - .file_name() - .unwrap_or_default() - .to_string_lossy() - .into_owned(); - - let relative_path = crate::utils::fs::path_as_url(&image.relative_path); + let image_key = crate::utils::fs::path_as_url(&image.relative_path); - if manifest_entries.contains_key(&image_file_name) { - collisions - .entry(image_file_name) - .or_default() - .push(relative_path); + if manifest_entries.contains_key(&image_key) { + duplicates.push(image_key); continue; } @@ -392,29 +381,19 @@ fn upload_images( }); extra.insert("content_hash".to_owned(), serde_json::Value::String(hash)); - kept_paths.insert(image_file_name.clone(), relative_path); uploads.push(PreparedImage { path: image.path, key, }); manifest_entries.insert( - image_file_name, + image_key, ImageMetadata::new(image.width, image.height, extra), ); } - if !collisions.is_empty() { - let details: String = collisions - .iter() - .map(|(name, excluded)| { - let kept = &kept_paths[name]; - let all = std::iter::once(kept.as_str()) - .chain(excluded.iter().map(|s| s.as_str())) - .join(", "); - format!("\n {name}: {all}") - }) - .collect(); - warn!("Some images share identical file names. Only the first occurrence of each is included:{details}"); + if !duplicates.is_empty() { + let paths = duplicates.join(", "); + warn!("Duplicate paths encountered, skipping: {paths}"); } let total_count = uploads.len(); From 1486e9c14197970490eddc297251527dfffadfdf Mon Sep 17 00:00:00 2001 From: Nico Hinderling Date: Wed, 15 Apr 2026 12:57:04 -0700 Subject: [PATCH 2/2] docs(changelog): Add entry for snapshot path fix Co-Authored-By: Claude --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1774705249..cc17783e73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Fixes - (snapshots) Chunk image uploads to avoid file descriptor exhaustion and 413 errors when uploading hundreds of images ([#3249](https://github.com/getsentry/sentry-cli/pull/3249)) +- (snapshots) Preserve subdirectory structure in snapshot manifest keys instead of flattening to bare filenames ([#3269](https://github.com/getsentry/sentry-cli/pull/3269)) - Replace `eprintln!` with `log::info!` for progress bar completion messages when the progress bar is disabled (e.g. in CI). This avoids spurious stderr output that some CI systems treat as errors ([#3223](https://github.com/getsentry/sentry-cli/pull/3223)). ## 3.3.5