Skip to content

Commit 1a31c9b

Browse files
committed
fix(celery): Propagate user-set headers
1 parent 13157f7 commit 1a31c9b

2 files changed

Lines changed: 62 additions & 0 deletions

File tree

sentry_sdk/integrations/celery/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,12 @@ def _update_celery_task_headers(
231231
if key.startswith("sentry-"):
232232
updated_headers["headers"][key] = value
233233

234+
# Preserve user-provided custom headers in the inner "headers" dict
235+
# so they survive to task.request.headers on the worker (celery#4875).
236+
for key, value in original_headers.items():
237+
if key != "headers" and key not in updated_headers["headers"]:
238+
updated_headers["headers"][key] = value
239+
234240
return updated_headers
235241

236242

tests/integrations/celery/test_celery.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,62 @@ def test_send_task_wrapped(
847847
assert span["trace_id"] == kwargs["headers"]["sentry-trace"].split("-")[0]
848848

849849

850+
def test_user_custom_headers_accessible_in_task(init_celery):
851+
"""
852+
Regression test for https://github.com/getsentry/sentry-python/issues/5566
853+
854+
User-provided custom headers passed to apply_async() must be accessible
855+
via task.request.headers on the worker side.
856+
"""
857+
celery = init_celery(traces_sample_rate=1.0)
858+
859+
@celery.task(name="custom_headers_task", bind=True)
860+
def custom_headers_task(self):
861+
return dict(self.request.headers or {})
862+
863+
custom_headers = {
864+
"my_custom_key": "my_value",
865+
"correlation_id": "abc-123",
866+
"tenant_id": "tenant-42",
867+
}
868+
869+
with start_transaction(name="test"):
870+
result = custom_headers_task.apply_async(headers=custom_headers)
871+
872+
received_headers = result.get()
873+
for key, value in custom_headers.items():
874+
assert received_headers.get(key) == value, (
875+
f"Custom header {key!r} not found in task.request.headers"
876+
)
877+
878+
879+
def test_user_custom_headers_do_not_overwrite_sentry_headers(init_celery):
880+
"""
881+
If a user passes a header with a key that collides with a Sentry header,
882+
the Sentry-generated value must take precedence.
883+
"""
884+
celery = init_celery(traces_sample_rate=1.0)
885+
886+
@celery.task(name="headers_precedence_task", bind=True)
887+
def headers_precedence_task(self):
888+
return dict(self.request.headers or {})
889+
890+
with start_transaction(name="test"):
891+
result = headers_precedence_task.apply_async(
892+
headers={
893+
"sentry-trace": "user-supplied-value",
894+
"my_custom_key": "my_value",
895+
},
896+
)
897+
898+
received_headers = result.get()
899+
# Sentry's own sentry-trace must NOT be the user-supplied value
900+
assert received_headers.get("sentry-trace") != "user-supplied-value"
901+
assert "sentry-trace" in received_headers
902+
# User custom header is still preserved
903+
assert received_headers.get("my_custom_key") == "my_value"
904+
905+
850906
@pytest.mark.skip(reason="placeholder so that forked test does not come last")
851907
def test_placeholder():
852908
"""Forked tests must not come last in the module.

0 commit comments

Comments
 (0)