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

vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 763cb20ddc1 Fix SageMaker credential failures being reported as 
timeouts (#74867)
763cb20ddc1 is described below

commit 763cb20ddc1ce25475bec64b479675ea0fc03bb2
Author: Mohamed Laghdaf Dadda <[email protected]>
AuthorDate: Fri Oct 23 13:44:27 2026 +0100

    Fix SageMaker credential failures being reported as timeouts (#74867)
---
 .../providers/amazon/aws/triggers/sagemaker.py     |  9 ++-
 .../unit/amazon/aws/triggers/test_sagemaker.py     | 65 +++++++++++++++++++++-
 2 files changed, 71 insertions(+), 3 deletions(-)

diff --git 
a/providers/amazon/src/airflow/providers/amazon/aws/triggers/sagemaker.py 
b/providers/amazon/src/airflow/providers/amazon/aws/triggers/sagemaker.py
index cbaeb7a1c04..cc4725e8257 100644
--- a/providers/amazon/src/airflow/providers/amazon/aws/triggers/sagemaker.py
+++ b/providers/amazon/src/airflow/providers/amazon/aws/triggers/sagemaker.py
@@ -27,7 +27,11 @@ from typing import TYPE_CHECKING
 from botocore.exceptions import WaiterError
 
 from airflow.exceptions import AirflowProviderDeprecationWarning
-from airflow.providers.amazon.aws.exceptions import WaiterMaxAttemptsError, 
WaiterTerminalFailure
+from airflow.providers.amazon.aws.exceptions import (
+    WaiterMaxAttemptsError,
+    WaiterNoCredentialsError,
+    WaiterTerminalFailure,
+)
 from airflow.providers.amazon.aws.hooks.sagemaker import SageMakerHook
 from airflow.providers.amazon.aws.triggers.base import AwsBaseWaiterTrigger
 from airflow.providers.common.compat.sdk import AirflowException
@@ -111,6 +115,9 @@ class SageMakerTrigger(AwsBaseWaiterTrigger):
 
     def _event_from_exception(self, error: AirflowException) -> TriggerEvent:
 
+        if isinstance(error, WaiterNoCredentialsError):
+            return super()._event_from_exception(error)
+
         if isinstance(error, WaiterMaxAttemptsError):
             return TriggerEvent(
                 {
diff --git a/providers/amazon/tests/unit/amazon/aws/triggers/test_sagemaker.py 
b/providers/amazon/tests/unit/amazon/aws/triggers/test_sagemaker.py
index b736505de3c..2dde688b16b 100644
--- a/providers/amazon/tests/unit/amazon/aws/triggers/test_sagemaker.py
+++ b/providers/amazon/tests/unit/amazon/aws/triggers/test_sagemaker.py
@@ -20,10 +20,14 @@ from unittest import mock
 from unittest.mock import AsyncMock
 
 import pytest
-from botocore.exceptions import WaiterError
+from botocore.exceptions import NoCredentialsError, WaiterError
 
 from airflow.exceptions import AirflowProviderDeprecationWarning
-from airflow.providers.amazon.aws.exceptions import WaiterMaxAttemptsError, 
WaiterTerminalFailure
+from airflow.providers.amazon.aws.exceptions import (
+    WaiterMaxAttemptsError,
+    WaiterNoCredentialsError,
+    WaiterTerminalFailure,
+)
 from airflow.providers.amazon.aws.hooks.sagemaker import SageMakerHook
 from airflow.providers.amazon.aws.triggers.sagemaker import 
SageMakerPipelineTrigger, SageMakerTrigger
 from airflow.providers.common.compat.sdk import AirflowException
@@ -212,6 +216,10 @@ class TestSagemakerTrigger:
                 ),
                 id="unknown-status",
             ),
+            pytest.param(
+                WaiterNoCredentialsError("SageMaker job failed"),
+                id="no-credentials",
+            ),
             pytest.param(
                 AirflowException("SageMaker job failed"),
                 id="generic-error",
@@ -235,6 +243,59 @@ class TestSagemakerTrigger:
             }
         )
 
+    @pytest.mark.asyncio
+    @pytest.mark.parametrize(
+        ("errors", "expected_status", "expected_message"),
+        [
+            pytest.param(
+                [NoCredentialsError(), NoCredentialsError()],
+                "error",
+                "Waiter error: max attempts reached due to missing 
credentials: Unable to locate credentials",
+                id="no-credentials",
+            ),
+            pytest.param(
+                [
+                    WaiterError("test_waiter", "Max attempts exceeded", {}),
+                    WaiterError("test_waiter", "Max attempts exceeded", {}),
+                ],
+                "timeout",
+                "Waiter error: max attempts reached",
+                id="ordinary-exhaustion",
+            ),
+            pytest.param(
+                [WaiterError("test_waiter", "terminal failure", 
{"TrainingJobStatus": "Failed"})],
+                "failed",
+                "Error while waiting for training job: Failed\nWaiter 
test_waiter failed: terminal failure",
+                id="terminal-failure",
+            ),
+            pytest.param(
+                [AirflowException("Unexpected failure")],
+                "error",
+                "Unexpected failure",
+                id="generic-error",
+            ),
+        ],
+    )
+    
@mock.patch("airflow.providers.amazon.aws.hooks.sagemaker.SageMakerHook.get_waiter")
+    
@mock.patch("airflow.providers.amazon.aws.hooks.sagemaker.SageMakerHook.get_async_conn")
+    async def test_run_waiter_errors(
+        self, mock_async_conn, mock_get_waiter, errors, expected_status, 
expected_message
+    ):
+        mock_get_waiter.return_value.wait = AsyncMock(side_effect=errors)
+        trigger = SageMakerTrigger(
+            job_name=JOB_NAME,
+            job_type=JOB_TYPE,
+            waiter_delay=0,
+            waiter_max_attempts=2,
+            aws_conn_id=AWS_CONN_ID,
+        )
+
+        events = [event async for event in trigger.run()]
+
+        assert events == [
+            TriggerEvent({"status": expected_status, "job_name": JOB_NAME, 
"message": expected_message})
+        ]
+
 
 class TestSagemakerPipelineTrigger:
     def test_serialize(self):

Reply via email to