Apache Airflow 3.3.1 shipped on 12 August 2026. It is a patch on the 3.3 series. The headline change is DataFrame XCom serialization under pandas 3: the class name stored in the metadata database now follows the pandas version of the component that pushed the value.
The full release notes and downloads are on the GitHub release page.
Pandas 3 DataFrame XCom class names ¶
pandas 3 exposes public classes from the pandas namespace. A DataFrame is pandas.DataFrame instead of pandas.core.frame.DataFrame. XComs store that name next to the serialized value, so the metadata row depends on the pandas version of the pusher. After this tag Airflow registers both names. A DataFrame written by either pandas version can be read by either. Existing XComs stay readable. No configuration change is required. Every Airflow component must be on 3.3.1 first.
Roll this version out to every component before pandas 3 reaches any of them, workers in particular. A component that predates this change cannot read a DataFrame XCom written under pandas 3. The pull fails with:
ImportError: pandas.DataFrame was not found in allow list for deserialization imports.
To allow it, add it to allowed_deserialization_classes in the configuration
The message points at configuration. The allow list is not the cause. Editing allowed_deserialization_classes does not help. The rows are not corrupt. They become readable as soon as the reader is upgraded. A downgrade is a one way door: rolling back strands any DataFrame XCom written on pandas 3 until the cluster rolls forward.
Dags that inspect dtypes on a pulled DataFrame need a review. The reader pandas version determines the frame, not the writer. Under pandas 3 a string column comes back as str rather than object, and missing values come back as nan rather than None. Values are unchanged. Code that branches on dtype == "object", tests cells with is None, or uses DataFrame.equals() can change behavior. Tracked as #71169.
Custom Dag bundle rows after a 2.x upgrade ¶
The 0082_3_1_0_make_bundle_name_not_nullable migration set every legacy row to bundle_name='dags-folder'. Triggering a DagRun then raised Requested bundle 'dags-folder' is not configured. on deployments that use any other bundle.
DagFileProcessorManager now runs a one shot, best effort backfill at startup and routes each affected Dag by file path. Unmatched Dags self heal on the next successful parse, or immediately via airflow dags reserialize. Legacy Dags with a NULL bundle_name are deactivated during a 2.x to 3.x upgrade (#70662). The backfill is #70994.
Team scoped sensitive configuration ¶
Sensitive options were masked only under their base section. A team scoped override in a [<team>=<section>] config file section, or through an AIRFLOW__<TEAM>___<SECTION>__<KEY> environment variable, was returned in full. Sensitivity is now resolved back to the base option, so the team value is masked the same way.
AirflowConfigParser.as_dict(display_sensitive=False), GET /config, GET /config/section/{section}/option/{option}, and airflow config list now return < hidden > for those team values. Use display_sensitive=True when a real value is required. Team scoped _cmd and _secret entries are replaced with < hidden > in place and are not resolved. Non team configuration is unchanged. The mask change is #71099.
Scheduler, Triggerer, and asset correctness ¶
Deferred task instances whose saved state cannot be resumed now fail instead of sitting stuck (#71183). Deferrable tasks that fail through a trigger emitted TaskFailedEvent now respect retries (#71163). Task callbacks no longer skip when TriggerDagRunOperator gets a 404 (#71083). The scheduler no longer fires on_failure_callback for heartbeat timed out retries (#69824). Mapped task groups no longer skip none_failed_min_one_success tasks (#70318).
Asset materialization was dropping the partition date on partitioned Dag runs (#69339). Asset triggered Dags now honor catchup for historical asset events (#69224). Monthly and yearly schedules with catchup disabled were drifting data intervals (#69189). Slow asset listeners on large fan outs caused lock contention and statement timeouts (#71065).
The Triggerer crashed when unused trigger cleanup lagged (#70668) and when json_logs was enabled (#70669). Task worker communication deadlocks are detected instead of hanging (#70744). Invalid trigger Dag run requests, SQLite backfill locks, empty backfill windows, and rendered fields on stale task instances now get explicit API status codes instead of a 500.
Upgrade notes ¶
Install 3.3.1 on every component before any of them moves to pandas 3. A webserver or Triggerer that still pulls DataFrame XComs will fail if workers write pandas 3 frames first.
Do not plan a downgrade once those XComs exist. If the cluster came from 2.x with a custom Dag bundle, confirm bundle_name is not stuck on dags-folder and run airflow dags reserialize if needed. Audit config dumps: team scoped secrets now show as < hidden > unless display_sensitive=True is set.
PyPI, docs, Docker, and constraint files for this tag are listed on the GitHub release page and in the upstream release notes.
Where to get it ¶
- GitHub release page
- apache/airflow repository
- Tag
3.3.1 - PyPI
- Docs
- Constraint files
- Image:
docker pull apache/airflow:3.3.1