This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-3-test by this push:
     new e5f859778c5 [v3-3-test] Stop variables export/import from silently 
corrupting values (#70944) (#71791)
e5f859778c5 is described below

commit e5f859778c56b38d71829e835db8d0126c04d2eb
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sun Aug 30 02:01:49 2026 +0200

    [v3-3-test] Stop variables export/import from silently corrupting values 
(#70944) (#71791)
---
 .../src/airflow/cli/commands/variable_command.py   |  5 +++-
 .../unit/cli/commands/test_variable_command.py     | 34 ++++++++++++++++++++++
 2 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/airflow-core/src/airflow/cli/commands/variable_command.py 
b/airflow-core/src/airflow/cli/commands/variable_command.py
index 194b02b529a..8cdf5490d3a 100644
--- a/airflow-core/src/airflow/cli/commands/variable_command.py
+++ b/airflow-core/src/airflow/cli/commands/variable_command.py
@@ -186,11 +186,14 @@ def variables_export(args):
 
         data = json.JSONDecoder()
         for var in qry:
+            # Mirror variables_import's reconstruction so export/import 
round-trips.
             try:
                 val = data.decode(var.val)
             except Exception:
                 val = var.val
-            if var.description:
+            if isinstance(val, str):
+                val = var.val
+            if var.description or (isinstance(val, dict) and "value" in val):
                 var_dict[var.key] = {
                     "value": val,
                     "description": var.description,
diff --git a/airflow-core/tests/unit/cli/commands/test_variable_command.py 
b/airflow-core/tests/unit/cli/commands/test_variable_command.py
index a02c95aa317..ac8eb11a3ae 100644
--- a/airflow-core/tests/unit/cli/commands/test_variable_command.py
+++ b/airflow-core/tests/unit/cli/commands/test_variable_command.py
@@ -360,6 +360,40 @@ class TestCliVariables:
         """Test variables_export command"""
         variable_command.variables_export(self.parser.parse_args(["variables", 
"export", os.devnull]))
 
+    @pytest.mark.parametrize(
+        ("stored_value", "expected_export"),
+        [
+            pytest.param(
+                '{"value": "a", "description": "b"}',
+                {"value": {"value": "a", "description": "b"}, "description": 
None},
+                id="envelope_lookalike",
+            ),
+            pytest.param(
+                '{"value": 1, "other": 2}',
+                {"value": {"value": 1, "other": 2}, "description": None},
+                id="envelope_lookalike_with_extra_keys",
+            ),
+            pytest.param('"hello"', '"hello"', id="json_string"),
+        ],
+    )
+    def test_variables_export_survives_reimport(self, tmp_path, stored_value, 
expected_export):
+        """Values that collide with the export format must round-trip through 
export/import."""
+        path = tmp_path / "variables.json"
+        variable_command.variables_set(self.parser.parse_args(["variables", 
"set", "k", stored_value]))
+        variable_command.variables_export(self.parser.parse_args(["variables", 
"export", os.fspath(path)]))
+
+        assert json.loads(path.read_text()) == {"k": expected_export}
+
+        variable_command.variables_delete(self.parser.parse_args(["variables", 
"delete", "k"]))
+        with create_session() as session:
+            variable_command.variables_import(
+                self.parser.parse_args(["variables", "import", 
os.fspath(path)]), session=session
+            )
+
+        assert Variable.get("k", deserialize_json=True) == 
json.loads(stored_value)
+        with create_session() as session:
+            assert 
session.scalar(select(Variable.description).where(Variable.key == "k")) is None
+
     def test_variables_isolation(self, tmp_path):
         """Test isolation of variables"""
         path1 = tmp_path / "testfile1.json"

Reply via email to