From b3d581f999131238b8d51e1d2679e0780b00b19c Mon Sep 17 00:00:00 2001 From: Lucas Jia Date: Mon, 27 Jul 2026 14:01:04 -0700 Subject: [PATCH] fix(sagemaker-core): use caller session for sagemaker control-plane client SageMakerClient.__init__ built the "sagemaker" control-plane client from a fresh default botocore session (botocore.session.get_session()) instead of the caller-provided session. The custom data loader for the pre-GA Job APIs needs a botocore session to attach to, and the new session it created silently dropped the caller's credentials/profile. Since SageMakerClient is a singleton, that default-credential client was reused process-wide, so control-plane calls went out under the ambient default AWS profile. When the execution role lived in a different account than the default profile, CreateTrainingJob failed with "RoleArn: Cross-account pass role is not allowed". This regressed V2 behavior and was introduced in the MTRL launch (#5919, first shipped in v3.13.0). Register the custom data loader on the caller session's underlying botocore session (session._session) and build the sagemaker client from that session, so it keeps the caller's credentials while still loading the custom service model. The other clients already used the passed session and are unchanged. --- sagemaker-core/src/sagemaker/core/utils/utils.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sagemaker-core/src/sagemaker/core/utils/utils.py b/sagemaker-core/src/sagemaker/core/utils/utils.py index 865ae0f96e..5ac288eab7 100644 --- a/sagemaker-core/src/sagemaker/core/utils/utils.py +++ b/sagemaker-core/src/sagemaker/core/utils/utils.py @@ -383,8 +383,13 @@ def __init__( # botocore release. Once these APIs ship in the official botocore # service-2.json, this custom loader is unnecessary and the standard # session.client("sagemaker", endpoint_url=...) path will work. + # + # NOTE: The custom data loader is registered on the caller-provided + # session's underlying botocore session so that the "sagemaker" + # control-plane client keeps the caller's credentials/profile. Using a + # fresh botocore.session.get_session() here would silently fall back to + # the default AWS profile and break cross-account calls (issue #6069). import botocore.loaders - import botocore.session as bc_session_mod import pathlib # Look for bundled service model inside the package first, @@ -392,15 +397,14 @@ def __init__( _bundled = pathlib.Path(__file__).resolve().parent.parent / "data" / "sample" _source_tree = pathlib.Path(__file__).resolve().parent.parent.parent.parent.parent / "sample" sample_model_dir = str(_bundled if _bundled.exists() else _source_tree) - bc_session = bc_session_mod.get_session() loader = botocore.loaders.Loader( extra_search_paths=[sample_model_dir], include_default_search_paths=True, ) - bc_session.register_component('data_loader', loader) - custom_session = Session(botocore_session=bc_session, region_name=env_region) + # boto3.Session exposes its underlying botocore session as `_session`. + session._session.register_component('data_loader', loader) - self.sagemaker_client = custom_session.client( + self.sagemaker_client = session.client( "sagemaker", region_name=env_region, endpoint_url=endpoint_url,