Skip to content

[webpack-plugin 5.4.0] 'disable-upload' does not stamp emitted source maps, so a later manual upload has no debug IDs #949

Description

@Stun3R

Environment

  • @sentry/webpack-plugin 5.4.0 (latest), @sentry/bundler-plugins 10.70.0
  • @sentry/cli 2.58.6
  • webpack 5.107.2, Node 22, devtool: 'hidden-source-map'
  • Sentry SaaS

Steps to Reproduce

  1. Build with the upload disabled (disable: 'disable-upload') but injection left on:

    // webpack.config.js
    const path = require('path');
    const { sentryWebpackPlugin } = require('@sentry/webpack-plugin');
    
    module.exports = {
        mode: 'production',
        entry: path.resolve(__dirname, 'src/index.js'),
        devtool: 'hidden-source-map',
        output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' },
        plugins: [
            sentryWebpackPlugin({
                org: 'my-org',
                project: 'my-project',
                authToken: process.env.SENTRY_AUTH_TOKEN,
                sourcemaps: { disable: 'disable-upload' },
            }),
        ],
    };
  2. npx webpack

  3. Inspect the emitted artifacts.

  4. Do the "manual upload at a later point in time" the option is documented for:

    $ sentry-cli sourcemaps upload --org my-org --project my-project \
        --release 1.0.0 --debug-id-reference --strict dist

    (--debug-id-reference is needed because the emitted .js carries only the runtime
    marker and no //# debugId= comment, so the CLI has to read the ID from the map.)

  5. Read the result back:

    $ curl -H "Authorization: Bearer $TOKEN" \
        ".../projects/my-org/my-project/files/artifact-bundles/?query=1.0.0"
    $ curl -H "Authorization: Bearer $TOKEN" \
        ".../projects/my-org/my-project/artifact-bundles/<bundleId>/files/"

Expected Result

Per the TSDoc for sourcemaps.disable:

If set to "disable-upload", the plugin will not upload sourcemaps to Sentry, but will inject debug IDs into the build artifacts. This is useful if you want to manually upload sourcemaps to Sentry at a later point in time.

I expected the emitted artifacts to be in a state a later manual upload can use — i.e. the debug ID readable from the emitted .js and/or its .js.map — so that step 4 produces a bundle that symbolicates.

Actual Result

The bundle is injected, but the source map is not, so the manual upload produces an artifact bundle in which every entry has debugId: null and nothing symbolicates.

$ npx webpack
webpack 5.107.2 compiled successfully

$ grep -o 'sentry-dbid-[0-9a-f-]\{36\}' dist/bundle.js
sentry-dbid-555cf081-0e3a-4edf-bd14-4b561d9f20e5   # injected, as documented

$ grep -c '//# debugId=' dist/bundle.js
0     # no spec comment either

$ node -e 'console.log(Object.keys(require("./dist/bundle.js.map")).join(", "))'
version, file, mappings, sources, sourcesContent, names, sourceRoot
#    ^ no debug_id / debugId

Uploading that dist/ and reading it back (step 5), next to the two other paths for comparison:

upload path artifact names debugId on .js debugId on .js.map
plugin's own upload (disable-upload off) ~/<debugId>-0.js set set
sentry-cli ... --debug-id-reference dist on disable-upload output ~/bundle.js null null
same, after copying the marker's UUID into the map as debug_id/debugId ~/bundle.js set set

Cause

Stamping the map is part of the upload routine the flag switches off. prepareBundleForDebugIdUpload() writes both the //# debugId= comment and the map's debug_id into copies in a temp folder, uploads those, then deletes them — which is also why the first row above is named <debugId>-0.js rather than bundle.js.

uploadSourcemaps()'s own doc comment states it directly:

If prepareArtifacts is set to false, no preparation (e.g. adding //# debugId=... and writing adjusted source maps) is performed and no temp folder is used.

So disable-upload skips the preparation along with the upload, leaving undone precisely the half a manual upload depends on.

Why sentry-cli sourcemaps inject is not a general workaround

The docs recommend sentry-cli sourcemaps inject before upload for CLI uploads. Run after a disable-upload build, it fails in two independent ways:

  1. It mints a different ID. inject does not recognise the plugin's sentry-dbid- marker, so it generates a fresh ID from file content — measured fdffc1a1-b558-5928-bbca-0cf1e824d253 against the plugin's 82d6f1cb-eba2-41b5-bef9-b265d8b83aab for the same bundle. At runtime the SDK reports the plugin's ID out of window._sentryDebugIds while Sentry has indexed inject's, so nothing matches.
  2. It rewrites the emitted .js. Builds that pin subresource integrity computed during the compilation then break — we hash assets with webpack-assets-manifest and serve <script integrity="...">, so a post-build rewrite makes the browser refuse the bundle.

For builds using SRI, there is currently no supported way to complete disable-upload.

Suggestion

Any one of these would resolve it:

  1. Docs — state that only the bundles are injected, and that the maps must be stamped separately before a manual upload will work.
  2. Stamp the emitted maps when disable-upload is set. The ID is already known at that point (derived from chunk.contentHash.javascript), and adding debug_id/debugId to the emitted map is inert for other source map consumers — devtools ignore unknown fields, and we measured identical originalPositionFor results before and after. This would make the option behave as documented.
  3. Expose injectDebugIds() or prepareArtifacts through the bundler plugin options, so the preparation can run without the upload.

We currently ship a ~40-line webpack plugin doing option 2 for ourselves. Happy to open a PR if a maintainer indicates a preferred direction.

Possibly affected already

#927 and #868 both adopt disable-upload specifically in order to upload later with sentry-cli, which is the pattern above.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions