YES, duplicate uploads can occur, but only in specific scenarios. The colleague's concern is valid for certain build configurations.
Setup:
dist/
├── app.min.js (minified bundle)
└── app.min.js.map (sourcemap with sourcesContent)
What happens:
- User runs:
sentry-cli sourcemaps upload dist/ - Files collected:
app.min.jsandapp.min.js.map(both match default extensions) - Files uploaded: 2 files total
- Original sources are embedded in
sourcesContentfield of the sourcemap - Result: NO DUPLICATION ✅
This is the most common scenario. Build tools (webpack, rollup, esbuild, etc.) typically output:
- Minified/bundled files in the output directory
- Sourcemaps with
sourcesContentalready populated - Original source files remain in
src/or similar directories (not uploaded)
Setup:
dist/
├── app.js (original unbundled source)
├── app.min.js (minified bundle)
└── app.min.js.map (sourcemap referencing app.js, with sourcesContent)
What happens:
- User runs:
sentry-cli sourcemaps upload dist/ - Files collected: ALL
.jsand.mapfiles (perDEFAULT_EXTENSIONS)app.js✓app.min.js✓app.min.js.map✓
- Files uploaded: 3 files total
- Result: DUPLICATION
⚠️ app.jsis uploaded as a standalone fileapp.jsis ALSO embedded inapp.min.js.mapviasourcesContent
Setup:
dist/
├── app.tsx (original TypeScript source)
├── app.min.js (transpiled & minified)
└── app.min.js.map (sourcemap with sourcesContent)
What happens:
- User runs:
sentry-cli sourcemaps upload dist/ - Files collected: Only
app.min.jsandapp.min.js.map - Result: NO DUPLICATION ✅
app.tsxis NOT uploaded (doesn't match.jsor.mapextensions)- Original TypeScript source is only in
sourcesContent
From src/commands/sourcemaps/upload.rs:22:
const DEFAULT_EXTENSIONS: &[&str] = &["js", "cjs", "mjs", "map", "jsbundle", "bundle"];Any file matching these extensions in the upload path will be collected and uploaded.
When --no-rewrite is NOT set (default behavior), processor.rewrite() is called with:
let options = sourcemap::RewriteOptions {
load_local_source_contents: true,
strip_prefixes: prefixes,
..Default::default()
};The load_local_source_contents: true option tells the sourcemap library to:
- Read the original source files from disk (if they exist)
- Embed them into the sourcemap's
sourcesContentfield - This happens even if
sourcesContentalready exists
However, this does NOT prevent the original files from being uploaded if they were already collected.
From the code flow in src/utils/sourcemaps.rs and src/utils/file_upload.rs:
- Collection phase: All files matching extensions are added to
processor.sources - Rewrite phase: Sourcemaps get their
sourcesContentpopulated/updated - Upload phase: ALL files in
processor.sourcesare uploaded
The validation logic in validate_regular() (line 1236) shows this clearly:
if sm.get_source_contents(idx).is_some() || source_urls.contains(source_url) {
info!("validator found source ({source_url})");
}A source is considered valid if it's EITHER in sourcesContent OR in the uploaded files. Both conditions can be true simultaneously.
From tests/integration/_cases/sourcemaps/sourcemaps-upload-some-debugids.trycmd:
- Input:
tests/integration/_fixtures/upload_some_debugids(20 files) - Output: "Bundled 20 files for upload"
- Breakdown:
- 13 Scripts (.js files)
- 7 Source Maps (.map files)
The sourcemap server/chunks/1.js.map contains:
- 163 sources in the
sourcesfield - 163 entries in
sourcesContent(all populated, not null) - But the original source files (e.g.,
node_modules/@sentry/core/build/cjs/api.js) do NOT exist in the fixture directory
This demonstrates typical behavior where sourcemaps reference files via webpack:// URLs that don't exist locally, so no duplication occurs.
Duplication happens when all of these conditions are met:
- ✅ Original source files have
.js,.cjs, or.mjsextensions - ✅ Original source files are in the same directory tree as the upload path
- ✅ Sourcemaps already contain
sourcesContentfor these files - ✅ User uploads the entire directory without filtering
Low to Medium likelihood in practice because:
- Modern build tools (webpack, vite, rollup, esbuild) typically output minified files separate from source files
- Most projects have
dist/orbuild/folders containing only build outputs - TypeScript/JSX sources don't match the default extensions
- Sourcemaps often reference sources via
webpack://or similar URLs that don't exist as local files
However, it can occur if:
- Build configuration outputs both bundled and unbundled JS files to the same directory
- Developer explicitly copies source files to the dist folder
- Using a build setup that preserves original
.jsfiles alongside minified versions
If this is deemed a problem worth addressing:
Before uploading, check if a file is already embedded in any sourcemap's sourcesContent and skip uploading it as a standalone file.
Pros: Reduces upload size and duplication Cons: Complex logic; might break edge cases where both are intentionally needed
Clarify in documentation that users should:
- Only upload their
dist/orbuild/directories - Not mix original sources with build outputs in the upload path
- Use
--extflag to be more selective if needed
Pros: Simple, no code changes needed Cons: Relies on user behavior
Allow users to opt into skipping files that are already embedded in sourcemaps.
Pros: Gives users control; backward compatible Cons: Adds complexity; another flag to understand
The colleague's observation is correct: sentry-cli can upload sources twice when both the original source files and sourcemaps (with sourcesContent) are in the upload path. However, this is not the typical use case due to how modern build tools organize their output.
The current behavior is technically correct (uploading what was requested) but could be optimized for the edge case where duplication occurs.
Recommendation: Document the expected usage pattern and potentially add a warning when duplication is detected, rather than changing the default behavior which might break existing workflows.