Sentry before_send/transport dotted-path config never resolved to a callable in Task SDK (silently drops all events)

Open Beginner friendly
#69,464 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
python

Research direction

Start in task-sdk/src/airflow/sdk/execution_time/sentry/configured.py, specifically ConfiguredSentry.prepare_to_enrich_errors(), and compare its [sentry] option handling with the 2.11.0 airflow/sentry.py behavior described in the issue. Verify the configured before_send and transport values are resolved through conf.getimport() before sentry_sdk.init() receives them. Done means dotted paths become callable options even when the Sentry section has schema defaults, without dropping events.

Written by the indexing model from the issue text.

Description

kind:bug
Under which category would you file this issue?

Task SDK

Apache Airflow version

3.2.2 (also reproduced, unchanged, on 3.3.0rc2)

What happened and how to reproduce it?

When [sentry] before_send (e.g. AIRFLOW__SENTRY__BEFORE_SEND=<dotted.path>) is configured,
ConfiguredSentry.prepare_to_enrich_errors() in
task-sdk/src/airflow/sdk/execution_time/sentry/configured.py never resolves it into an
actual callable — it gets passed to sentry_sdk.init() as the raw string:

sentry_config_opts: dict[str, Any] = conf.getsection("sentry") or {}
if sentry_config_opts:
    sentry_config_opts.pop("sentry_on")
    old_way_dsn = sentry_config_opts.pop("sentry_dsn", None)
    new_way_dsn = sentry_config_opts.pop("dsn", None)
    dsn = old_way_dsn or new_way_dsn
    ...
else:
    dsn = None
    if before_send := conf.getimport("sentry", "before_send", fallback=None):
        sentry_config_opts["before_send"] = before_send
    if transport := conf.getimport("sentry", "transport", fallback=None):
        sentry_config_opts["transport"] = transport

if dsn:
    sentry_sdk.init(dsn=dsn, integrations=integrations, **sentry_config_opts)

conf.getsection("sentry") always returns a non-empty dict — it starts from the section's
schema defaults (sentry_on, sentry_dsn both have non-None defaults) — so the
if sentry_config_opts: branch is always taken, and the else branch (the only place
before_send/transport get resolved via conf.getimport()) is unreachable dead code.

As a result, sentry_sdk.init(before_send="my.dotted.path", ...) receives a plain string.
At event-capture time, sentry_sdk's Client._prepare_event calls before_send(event, hint),
raising TypeError: 'str' object is not callable. This is swallowed internally by
capture_internal_exceptions() (logged only via the internal sentry_sdk.errors logger) —
the event is silently dropped with no error surfaced and nothing delivered to Sentry.
Same issue applies to transport.

This is a regression from Airflow 2.x's airflow/sentry.py, where the equivalent
conf.getimport(...) calls ran unconditionally:

sentry_config_opts["before_send"] = conf.getimport("sentry", "before_send", fallback=None)
sentry_config_opts["transport"] = conf.getimport("sentry", "transport", fallback=None)

(see 2.11.0's airflow/sentry.py). This logic appears to have been ported into the Task SDK
reimplementation in #57032, but the branching structure changed such that the resolution
path became unreachable.

Steps to reproduce:

AIRFLOW__SENTRY__SENTRY_ON=True
AIRFLOW__SENTRY__SENTRY_DSN=<any DSN>
AIRFLOW__SENTRY__BEFORE_SEND=path.to.a.real.before_send.function
from airflow.sdk.execution_time.sentry.configured import ConfiguredSentry
cs = ConfiguredSentry()
cs.prepare_to_enrich_errors(executor_integration="")
import sentry_sdk
print(type(sentry_sdk.get_client().options.get("before_send")))
# prints <class 'str'> instead of the resolved function

Any subsequent sentry_sdk.capture_exception() / capture_message() call will silently
fail to deliver its event.

What you think should happen instead?

before_send/transport dotted paths configured via [sentry] should always be resolved
into real callables via conf.getimport(), regardless of what else is set in the [sentry]
section — matching 2.x behavior, e.g.:

sentry_config_opts: dict[str, Any] = conf.getsection("sentry") or {}
sentry_config_opts.pop("sentry_on", None)
old_way_dsn = sentry_config_opts.pop("sentry_dsn", None)
new_way_dsn = sentry_config_opts.pop("dsn", None)
dsn = old_way_dsn or new_way_dsn
if before_send := conf.getimport("sentry", "before_send", fallback=None):
    sentry_config_opts["before_send"] = before_send
if transport := conf.getimport("sentry", "transport", fallback=None):
    sentry_config_opts["transport"] = transport
Operating System

N/A — reproduced via the official apache/airflow:3.2.2-python3.12 and
apache/airflow:3.3.0rc2-python3.12 images.

Deployment

Other Docker-based deployment

Apache Airflow Provider(s)

No response

Versions of Apache Airflow Providers

No response

Official Helm Chart version

Not Applicable

Kubernetes Version

Not Applicable

Helm Chart configuration

Not Applicable

Docker Image customizations

Not Applicable — reproduced against the unmodified official images.

Anything else?

This is separate from #52368 / #65136 / #65161 (uncaught task exceptions not captured by
Sentry), which is fixed in 3.3.0. This before_send/transport resolution bug is a
different issue, still present unchanged in 3.3.0rc2.

Are you willing to submit PR?
  • Yes I am willing to submit a PR!
Code of Conduct
  • I agree to follow this project's Code of Conduct
Dominant language
Python
Stars
46.9k
Forks
17.9k
Avg merge
2d 5h
Merged PRs (30d)
480

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from apache/airflow

All issues in apache/airflow

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.