Skip to content

fix(celery): Propagate user-set headers#5581

Merged
sentrivana merged 5 commits intomasterfrom
ivana/fix-celery-headers
Mar 3, 2026
Merged

fix(celery): Propagate user-set headers#5581
sentrivana merged 5 commits intomasterfrom
ivana/fix-celery-headers

Conversation

@sentrivana
Copy link
Contributor

@sentrivana sentrivana commented Mar 3, 2026

Description

The SDK interferes with Celery logic that puts custom headers into request.headers in the worker. Currently, we only make internal sentry headers available there. Make sure to copy over any user-set headers as well.

Issues

Closes #5566

Reminders

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


Bug Fixes 🐛

  • (celery) Propagate user-set headers by sentrivana in #5581

Documentation 📚

  • Add set_attribute example to changelog by sentrivana in #5578

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

Codecov Results 📊

1707 passed | ⏭️ 184 skipped | Total: 1891 | Pass Rate: 90.27% | Execution Time: 2m 15s

📊 Comparison with Base Branch

Metric Change
Total Tests
Passed Tests
Failed Tests
Skipped Tests

✨ No test changes detected

All tests are passing successfully.

❌ Patch coverage is 0.00%. Project has 11872 uncovered lines.
✅ Project coverage is 39.98%. Comparing base (base) to head (head).

Files with missing lines (1)
File Patch % Lines
__init__.py 3.86% ⚠️ 224 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    39.98%    39.98%        —%
==========================================
  Files          189       189         —
  Lines        19776     19779        +3
  Branches      6762      6766        +4
==========================================
+ Hits          7907      7907         —
- Misses       11869     11872        +3
- Partials       391       391         —

Generated by Codecov Action

sentrivana and others added 4 commits March 3, 2026 12:43
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@sentrivana sentrivana marked this pull request as ready for review March 3, 2026 12:44
@sentrivana sentrivana requested a review from a team as a code owner March 3, 2026 12:44
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: User header propagation skipped when tracing is inactive
    • Moved custom-header mirroring outside the tracing-headers guard and ensured the inner headers dict is created when needed so user headers propagate even with empty Sentry trace headers.

Create PR

Or push these changes by commenting:

@cursor push df190b50ed
Preview (df190b50ed)
diff --git a/sentry_sdk/integrations/celery/__init__.py b/sentry_sdk/integrations/celery/__init__.py
--- a/sentry_sdk/integrations/celery/__init__.py
+++ b/sentry_sdk/integrations/celery/__init__.py
@@ -231,11 +231,11 @@
                 if key.startswith("sentry-"):
                     updated_headers["headers"][key] = value
 
-            # Preserve user-provided custom headers in the inner "headers" dict
-            # so they survive to task.request.headers on the worker (celery#4875).
-            for key, value in original_headers.items():
-                if key != "headers" and key not in updated_headers["headers"]:
-                    updated_headers["headers"][key] = value
+        # Preserve user-provided custom headers in the inner "headers" dict
+        # so they survive to task.request.headers on the worker (celery#4875).
+        for key, value in original_headers.items():
+            if key != "headers":
+                updated_headers.setdefault("headers", {}).setdefault(key, value)
 
     return updated_headers
This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

# so they survive to task.request.headers on the worker (celery#4875).
for key, value in original_headers.items():
if key != "headers" and key not in updated_headers["headers"]:
updated_headers["headers"][key] = value
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

User header propagation skipped when tracing is inactive

Medium Severity

The new loop that copies user-provided headers into the inner updated_headers["headers"] dict is placed inside the if headers: block (line 191), so it only runs when Sentry produces non-empty trace propagation headers (i.e., when an active Sentry transaction exists). When tracing is disabled or there is no active transaction, headers is an empty dict, the block is skipped entirely, and user custom headers are never copied to the inner dict — leaving the original issue (#5566) unfixed for non-tracing deployments.

Fix in Cursor Fix in Web

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please address this as well if applicable 🙏 @sentrivana

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell this is ok.

Celery itself has logic in there that actually moves the headers to the correct location as long as there are no headers. If tracing is disabled in the SDK, the SDK won't pre-populate the headers, and the default Celery logic will kick in and take care of it.

Only the case where the SDK actually populates some of the headers is problematic, since it causes Celery to skip the moving logic entirely. So we need to make sure that we either populate all or nothing.

# Preserve user-provided custom headers in the inner "headers" dict
# so they survive to task.request.headers on the worker (celery#4875).
for key, value in original_headers.items():
if key != "headers" and key not in updated_headers["headers"]:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: A KeyError occurs when using custom headers with apply_async if the SDK client's propagate_traces option is disabled, as updated_headers["headers"] is accessed before it is created.
Severity: HIGH

Suggested Fix

Ensure the updated_headers["headers"] dictionary exists before the new loop that attempts to access it. This can be done by calling updated_headers.setdefault("headers", {}) unconditionally before the loop that preserves original headers.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: sentry_sdk/integrations/celery/__init__.py#L237

Potential issue: A `KeyError` will occur when calling a Celery task's `apply_async` with
custom headers under specific configuration. The new code attempts to access
`updated_headers["headers"]` to preserve user-provided headers. However, this dictionary
key is only created if Sentry trace headers are generated. If the Sentry SDK client has
`propagate_traces=False` while the Celery integration has `propagate_traces=True` (the
default), no Sentry headers are generated, the `updated_headers["headers"]` key is never
created, and the new code will raise a `KeyError` when it tries to access it, causing
the task submission to fail.

Did we get this right? 👍 / 👎 to inform future reviews.

# so they survive to task.request.headers on the worker (celery#4875).
for key, value in original_headers.items():
if key != "headers" and key not in updated_headers["headers"]:
updated_headers["headers"][key] = value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please address this as well if applicable 🙏 @sentrivana

@sentrivana sentrivana merged commit 35070ce into master Mar 3, 2026
171 of 173 checks passed
@sentrivana sentrivana deleted the ivana/fix-celery-headers branch March 3, 2026 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Celery integration strips user custom headers from task.request.headers

2 participants