set_propagation_headers overwrites existing baggage header instead of merging #2894

Description

@jakubsomonday

Issue Description

Sentry::Utils::HttpTracing#set_propagation_headers unconditionally overwrites all propagation headers on outgoing HTTP requests using req[k] = v. While this is fine for the Sentry-specific sentry-trace header, the baggage header is a W3C standard shared across multiple systems. Any pre-existing baggage entries (e.g. set by OpenTelemetry, application code, or other instrumentation) are silently discarded.

The affected code is in sentry-ruby/lib/sentry/utils/http_tracing.rb#L14-L16:

defset_propagation_headers(req)Sentry.get_trace_propagation_headers&.each{ |k,v| req[k]=v}end

Reproduction Steps

  1. Set a custom baggage header on an outgoing HTTP request (e.g. routingKey=myvalue)
  2. Ensure propagate_traces is enabled in Sentry config and the target URL matches trace_propagation_targets
  3. Make the HTTP request using Net::HTTP, Faraday, or Excon
  4. Inspect the outgoing request headers
require"net/http"Sentry.initdo |config|
config.dsn="https://key@sentry.io/1"config.traces_sample_rate=1.0config.propagate_traces=trueenduri=URI("https://example.com/api")req=Net::HTTP::Get.new(uri)req["baggage"]="routingKey=myvalue,tenantId=123"# After Sentry instruments this request, the baggage header# will only contain sentry-* entries -- the original values are lostNet::HTTP.start(uri.hostname,uri.port,use_ssl: true){ |http| http.request(req)}

Expected Behavior

When a baggage header already exists on the outgoing request, Sentry should merge its entries with the existing value by joining them with a comma (,), as per the W3C Bagga
ge specification. The resulting header should look like:

sentry-trace_id=abc123,sentry-environment=production,routingKey=myvalue,tenantId=123

Actual Behavior

Sentry replaces the entire baggage header with only Sentry's entries. The resulting header contains:

sentry-trace_id=abc123,sentry-environment=production,sentry-release=xyz,sentry-public_key=key123

The original routingKey=myvalue,tenantId=123 entries are lost.

Ruby Version

All (not version-specific)

SDK Version

All current versions (verified on 6.5.0)

Integration and Its Version

No response

Sentry Config

No response

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    set_propagation_headers overwrites existing baggage header instead of merging #2894

    Description

    @jakubsomonday

    Issue Description

    Sentry::Utils::HttpTracing#set_propagation_headers unconditionally overwrites all propagation headers on outgoing HTTP requests using req[k] = v. While this is fine for the Sentry-specific sentry-trace header, the baggage header is a W3C standard shared across multiple systems. Any pre-existing baggage entries (e.g. set by OpenTelemetry, application code, or other instrumentation) are silently discarded.

    The affected code is in sentry-ruby/lib/sentry/utils/http_tracing.rb#L14-L16:

    defset_propagation_headers(req)Sentry.get_trace_propagation_headers&.each{ |k,v| req[k]=v}end

    Reproduction Steps

    1. Set a custom baggage header on an outgoing HTTP request (e.g. routingKey=myvalue)
    2. Ensure propagate_traces is enabled in Sentry config and the target URL matches trace_propagation_targets
    3. Make the HTTP request using Net::HTTP, Faraday, or Excon
    4. Inspect the outgoing request headers
    require"net/http"Sentry.initdo |config|
    config.dsn="https://key@sentry.io/1"config.traces_sample_rate=1.0config.propagate_traces=trueenduri=URI("https://example.com/api")req=Net::HTTP::Get.new(uri)req["baggage"]="routingKey=myvalue,tenantId=123"# After Sentry instruments this request, the baggage header# will only contain sentry-* entries -- the original values are lostNet::HTTP.start(uri.hostname,uri.port,use_ssl: true){ |http| http.request(req)}

    Expected Behavior

    When a baggage header already exists on the outgoing request, Sentry should merge its entries with the existing value by joining them with a comma (,), as per the W3C Bagga
    ge specification. The resulting header should look like:

    sentry-trace_id=abc123,sentry-environment=production,routingKey=myvalue,tenantId=123
    

    Actual Behavior

    Sentry replaces the entire baggage header with only Sentry's entries. The resulting header contains:

    sentry-trace_id=abc123,sentry-environment=production,sentry-release=xyz,sentry-public_key=key123
    

    The original routingKey=myvalue,tenantId=123 entries are lost.

    Ruby Version

    All (not version-specific)

    SDK Version

    All current versions (verified on 6.5.0)

    Integration and Its Version

    No response

    Sentry Config

    No response

    Metadata

    Metadata

    Assignees

    No one assigned

      Projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      set_propagation_headers overwrites existing baggage header instead of merging #2894

      Description

      @jakubsomonday

      Issue Description

      Sentry::Utils::HttpTracing#set_propagation_headers unconditionally overwrites all propagation headers on outgoing HTTP requests using req[k] = v. While this is fine for the Sentry-specific sentry-trace header, the baggage header is a W3C standard shared across multiple systems. Any pre-existing baggage entries (e.g. set by OpenTelemetry, application code, or other instrumentation) are silently discarded.

      The affected code is in sentry-ruby/lib/sentry/utils/http_tracing.rb#L14-L16:

      defset_propagation_headers(req)Sentry.get_trace_propagation_headers&.each{ |k,v| req[k]=v}end

      Reproduction Steps

      1. Set a custom baggage header on an outgoing HTTP request (e.g. routingKey=myvalue)
      2. Ensure propagate_traces is enabled in Sentry config and the target URL matches trace_propagation_targets
      3. Make the HTTP request using Net::HTTP, Faraday, or Excon
      4. Inspect the outgoing request headers
      require"net/http"Sentry.initdo |config|
      config.dsn="https://key@sentry.io/1"config.traces_sample_rate=1.0config.propagate_traces=trueenduri=URI("https://example.com/api")req=Net::HTTP::Get.new(uri)req["baggage"]="routingKey=myvalue,tenantId=123"# After Sentry instruments this request, the baggage header# will only contain sentry-* entries -- the original values are lostNet::HTTP.start(uri.hostname,uri.port,use_ssl: true){ |http| http.request(req)}

      Expected Behavior

      When a baggage header already exists on the outgoing request, Sentry should merge its entries with the existing value by joining them with a comma (,), as per the W3C Bagga
      ge specification. The resulting header should look like:

      sentry-trace_id=abc123,sentry-environment=production,routingKey=myvalue,tenantId=123
      

      Actual Behavior

      Sentry replaces the entire baggage header with only Sentry's entries. The resulting header contains:

      sentry-trace_id=abc123,sentry-environment=production,sentry-release=xyz,sentry-public_key=key123
      

      The original routingKey=myvalue,tenantId=123 entries are lost.

      Ruby Version

      All (not version-specific)

      SDK Version

      All current versions (verified on 6.5.0)

      Integration and Its Version

      No response

      Sentry Config

      No response

      Metadata

      Metadata

      Assignees

      No one assigned

        Projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        set_propagation_headers overwrites existing baggage header instead of merging #2894

        Description

        @jakubsomonday

        Issue Description

        Sentry::Utils::HttpTracing#set_propagation_headers unconditionally overwrites all propagation headers on outgoing HTTP requests using req[k] = v. While this is fine for the Sentry-specific sentry-trace header, the baggage header is a W3C standard shared across multiple systems. Any pre-existing baggage entries (e.g. set by OpenTelemetry, application code, or other instrumentation) are silently discarded.

        The affected code is in sentry-ruby/lib/sentry/utils/http_tracing.rb#L14-L16:

        defset_propagation_headers(req)Sentry.get_trace_propagation_headers&.each{ |k,v| req[k]=v}end

        Reproduction Steps

        1. Set a custom baggage header on an outgoing HTTP request (e.g. routingKey=myvalue)
        2. Ensure propagate_traces is enabled in Sentry config and the target URL matches trace_propagation_targets
        3. Make the HTTP request using Net::HTTP, Faraday, or Excon
        4. Inspect the outgoing request headers
        require"net/http"Sentry.initdo |config|
        config.dsn="https://key@sentry.io/1"config.traces_sample_rate=1.0config.propagate_traces=trueenduri=URI("https://example.com/api")req=Net::HTTP::Get.new(uri)req["baggage"]="routingKey=myvalue,tenantId=123"# After Sentry instruments this request, the baggage header# will only contain sentry-* entries -- the original values are lostNet::HTTP.start(uri.hostname,uri.port,use_ssl: true){ |http| http.request(req)}

        Expected Behavior

        When a baggage header already exists on the outgoing request, Sentry should merge its entries with the existing value by joining them with a comma (,), as per the W3C Bagga
        ge specification. The resulting header should look like:

        sentry-trace_id=abc123,sentry-environment=production,routingKey=myvalue,tenantId=123
        

        Actual Behavior

        Sentry replaces the entire baggage header with only Sentry's entries. The resulting header contains:

        sentry-trace_id=abc123,sentry-environment=production,sentry-release=xyz,sentry-public_key=key123
        

        The original routingKey=myvalue,tenantId=123 entries are lost.

        Ruby Version

        All (not version-specific)

        SDK Version

        All current versions (verified on 6.5.0)

        Integration and Its Version

        No response

        Sentry Config

        No response

        Metadata

        Metadata

        Assignees

        No one assigned

          Projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          set_propagation_headers overwrites existing baggage header instead of merging #2894

          Description

          @jakubsomonday

          Issue Description

          Sentry::Utils::HttpTracing#set_propagation_headers unconditionally overwrites all propagation headers on outgoing HTTP requests using req[k] = v. While this is fine for the Sentry-specific sentry-trace header, the baggage header is a W3C standard shared across multiple systems. Any pre-existing baggage entries (e.g. set by OpenTelemetry, application code, or other instrumentation) are silently discarded.

          The affected code is in sentry-ruby/lib/sentry/utils/http_tracing.rb#L14-L16:

          defset_propagation_headers(req)Sentry.get_trace_propagation_headers&.each{ |k,v| req[k]=v}end

          Reproduction Steps

          1. Set a custom baggage header on an outgoing HTTP request (e.g. routingKey=myvalue)
          2. Ensure propagate_traces is enabled in Sentry config and the target URL matches trace_propagation_targets
          3. Make the HTTP request using Net::HTTP, Faraday, or Excon
          4. Inspect the outgoing request headers
          require"net/http"Sentry.initdo |config|
          config.dsn="https://key@sentry.io/1"config.traces_sample_rate=1.0config.propagate_traces=trueenduri=URI("https://example.com/api")req=Net::HTTP::Get.new(uri)req["baggage"]="routingKey=myvalue,tenantId=123"# After Sentry instruments this request, the baggage header# will only contain sentry-* entries -- the original values are lostNet::HTTP.start(uri.hostname,uri.port,use_ssl: true){ |http| http.request(req)}

          Expected Behavior

          When a baggage header already exists on the outgoing request, Sentry should merge its entries with the existing value by joining them with a comma (,), as per the W3C Bagga
          ge specification. The resulting header should look like:

          sentry-trace_id=abc123,sentry-environment=production,routingKey=myvalue,tenantId=123
          

          Actual Behavior

          Sentry replaces the entire baggage header with only Sentry's entries. The resulting header contains:

          sentry-trace_id=abc123,sentry-environment=production,sentry-release=xyz,sentry-public_key=key123
          

          The original routingKey=myvalue,tenantId=123 entries are lost.

          Ruby Version

          All (not version-specific)

          SDK Version

          All current versions (verified on 6.5.0)

          Integration and Its Version

          No response

          Sentry Config

          No response

          Metadata

          Metadata

          Assignees

          No one assigned

            Projects

            Milestone

            No milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            set_propagation_headers overwrites existing baggage header instead of merging #2894

            Description

            @jakubsomonday

            Issue Description

            Sentry::Utils::HttpTracing#set_propagation_headers unconditionally overwrites all propagation headers on outgoing HTTP requests using req[k] = v. While this is fine for the Sentry-specific sentry-trace header, the baggage header is a W3C standard shared across multiple systems. Any pre-existing baggage entries (e.g. set by OpenTelemetry, application code, or other instrumentation) are silently discarded.

            The affected code is in sentry-ruby/lib/sentry/utils/http_tracing.rb#L14-L16:

            defset_propagation_headers(req)Sentry.get_trace_propagation_headers&.each{ |k,v| req[k]=v}end

            Reproduction Steps

            1. Set a custom baggage header on an outgoing HTTP request (e.g. routingKey=myvalue)
            2. Ensure propagate_traces is enabled in Sentry config and the target URL matches trace_propagation_targets
            3. Make the HTTP request using Net::HTTP, Faraday, or Excon
            4. Inspect the outgoing request headers
            require"net/http"Sentry.initdo |config|
            config.dsn="https://key@sentry.io/1"config.traces_sample_rate=1.0config.propagate_traces=trueenduri=URI("https://example.com/api")req=Net::HTTP::Get.new(uri)req["baggage"]="routingKey=myvalue,tenantId=123"# After Sentry instruments this request, the baggage header# will only contain sentry-* entries -- the original values are lostNet::HTTP.start(uri.hostname,uri.port,use_ssl: true){ |http| http.request(req)}

            Expected Behavior

            When a baggage header already exists on the outgoing request, Sentry should merge its entries with the existing value by joining them with a comma (,), as per the W3C Bagga
            ge specification. The resulting header should look like:

            sentry-trace_id=abc123,sentry-environment=production,routingKey=myvalue,tenantId=123
            

            Actual Behavior

            Sentry replaces the entire baggage header with only Sentry's entries. The resulting header contains:

            sentry-trace_id=abc123,sentry-environment=production,sentry-release=xyz,sentry-public_key=key123
            

            The original routingKey=myvalue,tenantId=123 entries are lost.

            Ruby Version

            All (not version-specific)

            SDK Version

            All current versions (verified on 6.5.0)

            Integration and Its Version

            No response

            Sentry Config

            No response

            Metadata

            Metadata

            Assignees

            No one assigned

              Projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              set_propagation_headers overwrites existing baggage header instead of merging #2894

              Description

              @jakubsomonday

              Issue Description

              Sentry::Utils::HttpTracing#set_propagation_headers unconditionally overwrites all propagation headers on outgoing HTTP requests using req[k] = v. While this is fine for the Sentry-specific sentry-trace header, the baggage header is a W3C standard shared across multiple systems. Any pre-existing baggage entries (e.g. set by OpenTelemetry, application code, or other instrumentation) are silently discarded.

              The affected code is in sentry-ruby/lib/sentry/utils/http_tracing.rb#L14-L16:

              defset_propagation_headers(req)Sentry.get_trace_propagation_headers&.each{ |k,v| req[k]=v}end

              Reproduction Steps

              1. Set a custom baggage header on an outgoing HTTP request (e.g. routingKey=myvalue)
              2. Ensure propagate_traces is enabled in Sentry config and the target URL matches trace_propagation_targets
              3. Make the HTTP request using Net::HTTP, Faraday, or Excon
              4. Inspect the outgoing request headers
              require"net/http"Sentry.initdo |config|
              config.dsn="https://key@sentry.io/1"config.traces_sample_rate=1.0config.propagate_traces=trueenduri=URI("https://example.com/api")req=Net::HTTP::Get.new(uri)req["baggage"]="routingKey=myvalue,tenantId=123"# After Sentry instruments this request, the baggage header# will only contain sentry-* entries -- the original values are lostNet::HTTP.start(uri.hostname,uri.port,use_ssl: true){ |http| http.request(req)}

              Expected Behavior

              When a baggage header already exists on the outgoing request, Sentry should merge its entries with the existing value by joining them with a comma (,), as per the W3C Bagga
              ge specification. The resulting header should look like:

              sentry-trace_id=abc123,sentry-environment=production,routingKey=myvalue,tenantId=123
              

              Actual Behavior

              Sentry replaces the entire baggage header with only Sentry's entries. The resulting header contains:

              sentry-trace_id=abc123,sentry-environment=production,sentry-release=xyz,sentry-public_key=key123
              

              The original routingKey=myvalue,tenantId=123 entries are lost.

              Ruby Version

              All (not version-specific)

              SDK Version

              All current versions (verified on 6.5.0)

              Integration and Its Version

              No response

              Sentry Config

              No response

              Metadata

              Metadata

              Assignees

              No one assigned

                Projects

                Milestone

                No milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                set_propagation_headers overwrites existing baggage header instead of merging #2894

                Description

                @jakubsomonday

                Issue Description

                Sentry::Utils::HttpTracing#set_propagation_headers unconditionally overwrites all propagation headers on outgoing HTTP requests using req[k] = v. While this is fine for the Sentry-specific sentry-trace header, the baggage header is a W3C standard shared across multiple systems. Any pre-existing baggage entries (e.g. set by OpenTelemetry, application code, or other instrumentation) are silently discarded.

                The affected code is in sentry-ruby/lib/sentry/utils/http_tracing.rb#L14-L16:

                defset_propagation_headers(req)Sentry.get_trace_propagation_headers&.each{ |k,v| req[k]=v}end

                Reproduction Steps

                1. Set a custom baggage header on an outgoing HTTP request (e.g. routingKey=myvalue)
                2. Ensure propagate_traces is enabled in Sentry config and the target URL matches trace_propagation_targets
                3. Make the HTTP request using Net::HTTP, Faraday, or Excon
                4. Inspect the outgoing request headers
                require"net/http"Sentry.initdo |config|
                config.dsn="https://key@sentry.io/1"config.traces_sample_rate=1.0config.propagate_traces=trueenduri=URI("https://example.com/api")req=Net::HTTP::Get.new(uri)req["baggage"]="routingKey=myvalue,tenantId=123"# After Sentry instruments this request, the baggage header# will only contain sentry-* entries -- the original values are lostNet::HTTP.start(uri.hostname,uri.port,use_ssl: true){ |http| http.request(req)}

                Expected Behavior

                When a baggage header already exists on the outgoing request, Sentry should merge its entries with the existing value by joining them with a comma (,), as per the W3C Bagga
                ge specification. The resulting header should look like:

                sentry-trace_id=abc123,sentry-environment=production,routingKey=myvalue,tenantId=123
                

                Actual Behavior

                Sentry replaces the entire baggage header with only Sentry's entries. The resulting header contains:

                sentry-trace_id=abc123,sentry-environment=production,sentry-release=xyz,sentry-public_key=key123
                

                The original routingKey=myvalue,tenantId=123 entries are lost.

                Ruby Version

                All (not version-specific)

                SDK Version

                All current versions (verified on 6.5.0)

                Integration and Its Version

                No response

                Sentry Config

                No response

                Metadata

                Metadata

                Assignees

                No one assigned

                  Projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions