This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-2-test by this push:
new f48dc4eccc6 [v3-2-test] fix: handle PermissionError in init_log_folder
for mounted filesystems (#63878) (#66733)
f48dc4eccc6 is described below
commit f48dc4eccc6b10f7ffd11b3470f60e5d70facf9e
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue May 12 05:10:37 2026 +0200
[v3-2-test] fix: handle PermissionError in init_log_folder for mounted
filesystems (#63878) (#66733)
* fix: handle PermissionError in init_log_folder for mounted filesystems
* fix: narrow OSError to PermissionError and add init_log_folder regression
test
* fix: remove redundant try/except in configure_logging and clean up test
* fix: broaden to OSError, rewrite test without caplog, add core
newsfragment
(cherry picked from commit 3ccf468a2ad3f4c4fae43907d5fd607c2027a047)
Co-authored-by: Pranay Kumar Karvi <[email protected]>
Co-authored-by: Jarek Potiuk <[email protected]>
---
airflow-core/newsfragments/63878.bugfix.rst | 1 +
shared/logging/src/airflow_shared/logging/structlog.py | 12 +++++++++---
shared/logging/tests/logging/test_structlog.py | 11 ++++++++++-
3 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/airflow-core/newsfragments/63878.bugfix.rst
b/airflow-core/newsfragments/63878.bugfix.rst
new file mode 100644
index 00000000000..f85875f7433
--- /dev/null
+++ b/airflow-core/newsfragments/63878.bugfix.rst
@@ -0,0 +1 @@
+Fixed ``airflow`` CLI commands (including ``airflow db migrate``) crashing
with ``PermissionError`` on startup when ``base_log_folder`` is configured to a
path on a mounted filesystem (e.g. NFS) whose parent directories the Airflow
user cannot create.
diff --git a/shared/logging/src/airflow_shared/logging/structlog.py
b/shared/logging/src/airflow_shared/logging/structlog.py
index d82ac60526f..7dddafe3b0a 100644
--- a/shared/logging/src/airflow_shared/logging/structlog.py
+++ b/shared/logging/src/airflow_shared/logging/structlog.py
@@ -756,9 +756,15 @@ def init_log_folder(directory: str | os.PathLike[str],
new_folder_permissions: i
user.
"""
directory = _PatchedPath(directory)
- for parent in reversed(_PatchedPath(directory).parents):
- parent.mkdir(mode=new_folder_permissions, exist_ok=True)
- directory.mkdir(mode=new_folder_permissions, exist_ok=True)
+ try:
+ directory.mkdir(mode=new_folder_permissions, parents=True,
exist_ok=True)
+ except OSError as e:
+ log.warning(
+ "Could not create log folder %s: %s. "
+ "Airflow will continue but logging to this directory may fail.",
+ directory,
+ e,
+ )
def init_log_file(
diff --git a/shared/logging/tests/logging/test_structlog.py
b/shared/logging/tests/logging/test_structlog.py
index 8904aa19326..5b5514b6864 100644
--- a/shared/logging/tests/logging/test_structlog.py
+++ b/shared/logging/tests/logging/test_structlog.py
@@ -25,6 +25,7 @@ import os
import sys
import textwrap
from datetime import datetime, timezone
+from pathlib import Path
from unittest import mock
import pytest
@@ -35,7 +36,7 @@ from structlog.processors import CallsiteParameter
from airflow_shared.logging import structlog as structlog_module
from airflow_shared.logging.structlog import configure_logging
-# We don't want to use the caplog fixture in this test, as the main purpose of
this file is to capture the
+# We avoid the caplog fixture for most tests here; the main purpose of this
file is to capture the
# _rendered_ output of the tests to make sure it is correct.
PY_3_11 = sys.version_info >= (3, 11)
@@ -512,6 +513,14 @@ def
test_excepthook_passes_keyboard_interrupt_to_original():
sys.excepthook = original
+def test_init_log_folder_does_not_raise_on_permission_error():
+ from airflow_shared.logging.structlog import init_log_folder
+
+ with mock.patch.object(Path, "mkdir", side_effect=PermissionError("not
allowed")):
+ # Must not raise — CLI commands like `airflow db migrate` rely on this.
+ init_log_folder("/tmp/blocked", 0o775)
+
+
class TestWarningsInterceptor:
@pytest.fixture(autouse=True)
def reset(self):