release notes
release notes
Published 5/29/2026
PatchContains breaking changes📦 PyPI: https://pypi.org/project/apache-airflow/3.2.2/ 📚 Docs: https://airflow.apache.org/docs/apache-airflow/3.2.2/ 🛠 Release Notes: https://airflow.apache.org/docs/apache-airflow/3.2.2/release_notes.html 🐳 Docker Image: "docker pull apache/airflow:3.2.2" 🚏 Constraints: https://github.com/apache/airflow/tree/constraints-3.2.2
The SMTP STARTTLS upgrade performed by airflow.utils.email.send_email now validates the SMTP server's certificate against the system's trusted CA bundle by default. Previously the starttls() call was made without an SSL context, so any certificate was accepted.
Deployments that intentionally point Airflow at an SMTP server with a self-signed or otherwise non-validating certificate and need to preserve the previous behaviour must set email.ssl_context = "none" in airflow.cfg. The "default" value (now also the default when the option is unset) uses :func:ssl.create_default_context. Previously this option applied only to the SMTP_SSL path; it now applies to the STARTTLS path as well. (#65346)
In #64963, the Airflow UI switched from full-match *_pattern REST API query parameters to the new index-friendly *_prefix_pattern parameters on list endpoints. This is a behavioral change for search-as-you-type filters in the UI: matches are prefix-based (LIKE 'term%' via a range scan) instead of substring-based (ILIKE '%term%'), which means the database can use B-tree indexes and search stays fast on large deployments. The REST API itself keeps both forms: existing *_pattern parameters still behave exactly as before.
In #66015, a per-search-bar "Match anywhere" toggle was added so users who relied on the previous substring behavior can opt back into it from the UI. Each search input and each text filter pill now has a small regex-icon toggle next to the value; flipping it on switches that input from *_prefix_pattern to *_pattern. (#66015)
Fix triggerer race condition and deadlock that caused deferred tasks to stall indefinitely
Triggers that call synchronous SDK methods (e.g. get_task_states used by
safe_to_cancel in several Google provider operators) could crash the triggerer's
internal subprocess. The triggerer would then continue to heartbeat normally —
appearing healthy to the scheduler — while silently processing zero triggers, causing
every deferred task to time out. This was first reported in issue #64620; a
partial fix shipped in Airflow 3.2.1 (#64882) but introduced a new deadlock
with the same visible symptom under load.
Both issues are fixed by replacing the lock-based serialization with response multiplexing: each request now carries a unique ID and the response is routed back to the correct caller, so concurrent requests from trigger threads no longer contend or deadlock regardless of how many triggers are running or what SDK methods they call.
New: triggerer subprocess watchdog
Even with the race fixed, a trigger that blocks the event loop (e.g. by calling
time.sleep() or performing blocking I/O directly in async def run()) would
previously leave the triggerer appearing healthy indefinitely.
A new [triggerer] runner_health_check_threshold config option (default: 30 seconds)
adds a watchdog: if the triggerer subprocess goes silent for longer than the threshold,
the parent process stops updating the heartbeat so the scheduler can detect the hang and
reassign triggers rather than waiting for them to individually time out. Set the option
to 0 to disable the watchdog. (#66412)
Tighten [core] allowed_deserialization_classes_regexp to require full-string matches
Patterns in [core] allowed_deserialization_classes_regexp are now matched
against the entire classname using re.fullmatch() instead of re.match().
Previously a pattern such as airflow\.models\.Variable admitted not only
the intended class but also names that started with it
(e.g. airflow.models.Variable_Malicious), because re.match only anchors
at the start of the string.
The default value of this option is empty, so out-of-the-box deployments are
unaffected. Deployments that configured this option with patterns relying on
prefix-match semantics — for example airflow\.models\. to mean "any class
under airflow.models" — must add .* to the pattern
(airflow\.models\..*) to retain the previous behaviour. (#66499)
Custom deadline reference classes must now be registered via the new deadline_references attribute on AirflowPlugin, matching the existing pattern for custom timetables and custom partition mappers. To use a custom DeadlineReference subclass, register it in a plugin's deadline_references list. Custom references that are not registered will raise DeadlineReferenceNotRegistered at deserialization. (#66737)
Callback.handle_event triggerer crash when OpenTelemetry metrics receive dict typed tag values (#67527) (#67529)modulepreload hrefs to the api-server static path (#67548) (#67556)external_executor_id with multiple executors on PostgreSQL (#67388) (#67458)starlette>=1.0.1 for Host-header parsing fix and cadwyn>=6.1.1 for compatibility
(#67326) (#67460)ti_update_state caused by FOR UPDATE locking dag_run (#67246) (#67264)getLatestRunInfo on paused Dags with no active runs (#67249) (#67256)_collect_teams_to_check and requires_access_backfill against malformed request bodies (#66504) (#67182)ValueError when supervisor force-closes stuck sockets after timeout (#67115) (#67162)XCom PATCH/POST to store native values instead of json.dumps output (#64220) (#67116)max_active_runs lost during Dag serialization when value equals schema default (#65310) (#67097)serialize_template_field handling callable value in dict (#63871) (#67092)ArgNotSet repr to use stable string instead of memory address (#65222) (#66897)XCom update payload (#65915) (#66913)removeprefix (#66749) (#66772)SIGSEGV in task execution by using fork + exec (#64874) (#66872)run_after (#65207) (#66863)pod_override serialization in Dag details and executor path (#65407) (#66898)pool_recycle and pool_pre_ping configuration (#65276) (#66866)DagVersion when clearing tasks with run on latest version (#65835) (#66901)DagRun already in target state (#66198) (#66919)LocalExecutor caused by unreleased file descriptor locks (#65121) (#66887)DagCalendarTab background color retrieval and loading overlay handling (#64189) (#66860)lazyMount causing JSON editor infinite loading (#65969) (#66828)ConnectionForm crashing when connection has invalid extra JSON (#66593) (#66831)PermissionError in init_log_folder for mounted filesystems (#63878) (#66733)StaleDataError in verify_integrity (#64503) (#66727)/tmp file leak when API server streams large task logs (#66450) (#66667)XCom prior-dates lookup for duplicate run_id across Dags (#65227) (#66646)/required_actions listing to show mapped task instances (#66433) (#66482)bundle_version when versioning disabled (#66485) (#66518)Next Run timestamp for paused Dags (#66552) (#66568)DagRun state is expired (#66339) (#66347)partition_date on partitioned backfill runs (#65998) (#66409)remote_task_handler_kwargs passing handler params to RemoteLogIO (#65957) (#66440)upstream_failed from failed in normal vision (#66324) (#66365)SearchBar input rewind (#66284) (#66359)logical_date when previous data_interval is zero-length (#66132) (#66263)autoincrement sequence on callback_request downgrade (#65230) (#66189)source_aliases in process_executor_events (#65422) (#66191)dagRuns API to honor start_date_gte filter correctly (#66045) (#66098)UniqueViolation crash on downgrade from 3.2.0 to 3.1.x (#65688) (#66003)ti.hostname is empty (#64285) (#65583)year field unmodifiable (#63885) (#65890)TypeError crashes on /users/list and /roles/list in FAB UI caused by concurrent API schema requests (#63986) (#65892)PoolBar links using wrong query params for task instances filtering (#64182) (#65896)pathlib sys.intern in long-running processes (#65706) (#65855)external_executor_id at queuing time to prevent duplicate execution on scheduler crash (#65594) (#65711)ti.start_date showing deferral-resume time instead of original start time (#63247) (#65491)map_index bounds validation (#64133) (#65479)XCom navigation from Grid (#65192) (#65322)is_url_safe to reject URLs with /// (#65557) (#65737)run_id_pattern pipe OR operator dropping single-term edge cases (#65190) (#65565)structure_data endpoint (#65342) (#65534)partitioned_dag_runs endpoints (#65344) (#65538)PATCH /dags pagination bug and document wildcard dag_id_pattern (#65309)Secure flag when request is HTTPS (#65348) (#65363)relative_fileloc and bundle (#65329) (#65343)requires_access_event_log to GET /eventLogs list endpoint (#67185) (#67211)Named*Logger.name working across structlog releases (#66875) (#67088)Checkbox (#66714) (#66826)ti_id, task_id, etc.) once, not on every log line (#66036) (#66421)TriggerDagRunOperator (#65747) (#66378)isExpanded prop on JSON expand/collapse buttons (#66340) (#66364)try_number to extra links API (#65661) (#66171)DagBag (#65775) (#65966)get_dag_runs endpoint (#65604) (#65746)XCom entries in the REST API and UI (#65418) (#65600)get_task_instances endpoint (#64845) (#65405)ti_summaries and grid runs queries (#64034) (#67014)from_timestamp from Task SDK timezone module (#67321) (#67331)plugins_manager docs (#67101) (#67114)(fr) UI translations to 100% coverage (#67241)airflow-core (#66703) (#66740)create_cron_data_intervals (#66458)zh-TW translations (#66401)core_api (#66211) (#66304)airflow-core/src/airflow/api (#66200) (#66214)BashOperator document (#64129) (#65850)release notes
Published 5/29/2026
PatchContains breaking changes📦 PyPI: https://pypi.org/project/apache-airflow/3.2.2/ 📚 Docs: https://airflow.apache.org/docs/apache-airflow/3.2.2/ 🛠 Release Notes: https://airflow.apache.org/docs/apache-airflow/3.2.2/release_notes.html 🐳 Docker Image: "docker pull apache/airflow:3.2.2" 🚏 Constraints: https://github.com/apache/airflow/tree/constraints-3.2.2
The SMTP STARTTLS upgrade performed by airflow.utils.email.send_email now validates the SMTP server's certificate against the system's trusted CA bundle by default. Previously the starttls() call was made without an SSL context, so any certificate was accepted.
Deployments that intentionally point Airflow at an SMTP server with a self-signed or otherwise non-validating certificate and need to preserve the previous behaviour must set email.ssl_context = "none" in airflow.cfg. The "default" value (now also the default when the option is unset) uses :func:ssl.create_default_context. Previously this option applied only to the SMTP_SSL path; it now applies to the STARTTLS path as well. (#65346)
In #64963, the Airflow UI switched from full-match *_pattern REST API query parameters to the new index-friendly *_prefix_pattern parameters on list endpoints. This is a behavioral change for search-as-you-type filters in the UI: matches are prefix-based (LIKE 'term%' via a range scan) instead of substring-based (ILIKE '%term%'), which means the database can use B-tree indexes and search stays fast on large deployments. The REST API itself keeps both forms: existing *_pattern parameters still behave exactly as before.
In #66015, a per-search-bar "Match anywhere" toggle was added so users who relied on the previous substring behavior can opt back into it from the UI. Each search input and each text filter pill now has a small regex-icon toggle next to the value; flipping it on switches that input from *_prefix_pattern to *_pattern. (#66015)
Fix triggerer race condition and deadlock that caused deferred tasks to stall indefinitely
Triggers that call synchronous SDK methods (e.g. get_task_states used by
safe_to_cancel in several Google provider operators) could crash the triggerer's
internal subprocess. The triggerer would then continue to heartbeat normally —
appearing healthy to the scheduler — while silently processing zero triggers, causing
every deferred task to time out. This was first reported in issue #64620; a
partial fix shipped in Airflow 3.2.1 (#64882) but introduced a new deadlock
with the same visible symptom under load.
Both issues are fixed by replacing the lock-based serialization with response multiplexing: each request now carries a unique ID and the response is routed back to the correct caller, so concurrent requests from trigger threads no longer contend or deadlock regardless of how many triggers are running or what SDK methods they call.
New: triggerer subprocess watchdog
Even with the race fixed, a trigger that blocks the event loop (e.g. by calling
time.sleep() or performing blocking I/O directly in async def run()) would
previously leave the triggerer appearing healthy indefinitely.
A new [triggerer] runner_health_check_threshold config option (default: 30 seconds)
adds a watchdog: if the triggerer subprocess goes silent for longer than the threshold,
the parent process stops updating the heartbeat so the scheduler can detect the hang and
reassign triggers rather than waiting for them to individually time out. Set the option
to 0 to disable the watchdog. (#66412)
Tighten [core] allowed_deserialization_classes_regexp to require full-string matches
Patterns in [core] allowed_deserialization_classes_regexp are now matched
against the entire classname using re.fullmatch() instead of re.match().
Previously a pattern such as airflow\.models\.Variable admitted not only
the intended class but also names that started with it
(e.g. airflow.models.Variable_Malicious), because re.match only anchors
at the start of the string.
The default value of this option is empty, so out-of-the-box deployments are
unaffected. Deployments that configured this option with patterns relying on
prefix-match semantics — for example airflow\.models\. to mean "any class
under airflow.models" — must add .* to the pattern
(airflow\.models\..*) to retain the previous behaviour. (#66499)
Custom deadline reference classes must now be registered via the new deadline_references attribute on AirflowPlugin, matching the existing pattern for custom timetables and custom partition mappers. To use a custom DeadlineReference subclass, register it in a plugin's deadline_references list. Custom references that are not registered will raise DeadlineReferenceNotRegistered at deserialization. (#66737)
Callback.handle_event triggerer crash when OpenTelemetry metrics receive dict typed tag values (#67527) (#67529)modulepreload hrefs to the api-server static path (#67548) (#67556)external_executor_id with multiple executors on PostgreSQL (#67388) (#67458)starlette>=1.0.1 for Host-header parsing fix and cadwyn>=6.1.1 for compatibility
(#67326) (#67460)ti_update_state caused by FOR UPDATE locking dag_run (#67246) (#67264)getLatestRunInfo on paused Dags with no active runs (#67249) (#67256)_collect_teams_to_check and requires_access_backfill against malformed request bodies (#66504) (#67182)ValueError when supervisor force-closes stuck sockets after timeout (#67115) (#67162)XCom PATCH/POST to store native values instead of json.dumps output (#64220) (#67116)max_active_runs lost during Dag serialization when value equals schema default (#65310) (#67097)serialize_template_field handling callable value in dict (#63871) (#67092)ArgNotSet repr to use stable string instead of memory address (#65222) (#66897)XCom update payload (#65915) (#66913)removeprefix (#66749) (#66772)SIGSEGV in task execution by using fork + exec (#64874) (#66872)run_after (#65207) (#66863)pod_override serialization in Dag details and executor path (#65407) (#66898)pool_recycle and pool_pre_ping configuration (#65276) (#66866)DagVersion when clearing tasks with run on latest version (#65835) (#66901)DagRun already in target state (#66198) (#66919)LocalExecutor caused by unreleased file descriptor locks (#65121) (#66887)DagCalendarTab background color retrieval and loading overlay handling (#64189) (#66860)lazyMount causing JSON editor infinite loading (#65969) (#66828)ConnectionForm crashing when connection has invalid extra JSON (#66593) (#66831)PermissionError in init_log_folder for mounted filesystems (#63878) (#66733)StaleDataError in verify_integrity (#64503) (#66727)/tmp file leak when API server streams large task logs (#66450) (#66667)XCom prior-dates lookup for duplicate run_id across Dags (#65227) (#66646)/required_actions listing to show mapped task instances (#66433) (#66482)bundle_version when versioning disabled (#66485) (#66518)Next Run timestamp for paused Dags (#66552) (#66568)DagRun state is expired (#66339) (#66347)partition_date on partitioned backfill runs (#65998) (#66409)remote_task_handler_kwargs passing handler params to RemoteLogIO (#65957) (#66440)upstream_failed from failed in normal vision (#66324) (#66365)SearchBar input rewind (#66284) (#66359)logical_date when previous data_interval is zero-length (#66132) (#66263)autoincrement sequence on callback_request downgrade (#65230) (#66189)source_aliases in process_executor_events (#65422) (#66191)dagRuns API to honor start_date_gte filter correctly (#66045) (#66098)UniqueViolation crash on downgrade from 3.2.0 to 3.1.x (#65688) (#66003)ti.hostname is empty (#64285) (#65583)year field unmodifiable (#63885) (#65890)TypeError crashes on /users/list and /roles/list in FAB UI caused by concurrent API schema requests (#63986) (#65892)PoolBar links using wrong query params for task instances filtering (#64182) (#65896)pathlib sys.intern in long-running processes (#65706) (#65855)external_executor_id at queuing time to prevent duplicate execution on scheduler crash (#65594) (#65711)ti.start_date showing deferral-resume time instead of original start time (#63247) (#65491)map_index bounds validation (#64133) (#65479)XCom navigation from Grid (#65192) (#65322)is_url_safe to reject URLs with /// (#65557) (#65737)run_id_pattern pipe OR operator dropping single-term edge cases (#65190) (#65565)structure_data endpoint (#65342) (#65534)partitioned_dag_runs endpoints (#65344) (#65538)PATCH /dags pagination bug and document wildcard dag_id_pattern (#65309)Secure flag when request is HTTPS (#65348) (#65363)relative_fileloc and bundle (#65329) (#65343)requires_access_event_log to GET /eventLogs list endpoint (#67185) (#67211)Named*Logger.name working across structlog releases (#66875) (#67088)Checkbox (#66714) (#66826)ti_id, task_id, etc.) once, not on every log line (#66036) (#66421)TriggerDagRunOperator (#65747) (#66378)isExpanded prop on JSON expand/collapse buttons (#66340) (#66364)try_number to extra links API (#65661) (#66171)DagBag (#65775) (#65966)get_dag_runs endpoint (#65604) (#65746)XCom entries in the REST API and UI (#65418) (#65600)get_task_instances endpoint (#64845) (#65405)ti_summaries and grid runs queries (#64034) (#67014)from_timestamp from Task SDK timezone module (#67321) (#67331)plugins_manager docs (#67101) (#67114)(fr) UI translations to 100% coverage (#67241)airflow-core (#66703) (#66740)create_cron_data_intervals (#66458)zh-TW translations (#66401)core_api (#66211) (#66304)airflow-core/src/airflow/api (#66200) (#66214)BashOperator document (#64129) (#65850)Apache Airflow - A platform to programmatically author, schedule, and monitor workflows