Skip to content

fix(core): recognize new duplicate-name error wording; add actionable train() errors - #6256

Open
jam-jee wants to merge 1 commit into
aws:masterfrom
jam-jee:fix/error-dx-improvements
Open

fix(core): recognize new duplicate-name error wording; add actionable train() errors#6256
jam-jee wants to merge 1 commit into
aws:masterfrom
jam-jee:fix/error-dx-improvements

Conversation

@jam-jee

@jam-jee jam-jee commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Problem

SDK telemetry shows the single largest V3 failure signature (about 40% of all recorded V3 failures over the last 90 days) is CreatePipeline/CreateExperiment rejecting duplicate names, even for callers using pipeline.upsert() which is supposed to handle exactly this case. Separately, ModelTrainer.train() failures are dominated by terminal service errors (ResourceLimitExceeded alone is 57% of them) that surface as raw botocore exceptions with no remediation guidance, which drives blind retry loops that cannot succeed.

Why it matters

  • upsert() and every load-or-create flow in the SDK are silently broken for the affected resource types: instead of loading or updating the existing resource, they re-raise, breaking documented create-or-update workflows (repeated CI/CD pipeline deployments, experiment runs).
  • Customers hitting quota exhaustion or missing region/credentials get no actionable path forward, inflating error rates and support load.

Fix (symptom -> root cause -> change)

Symptom: upsert() raises ValidationException on an existing pipeline. Root cause: the SageMaker service changed its duplicate-name error wording from ... already exists to ... names must be unique within an AWS account ..., and five separate call sites detect "resource already exists" by matching the old "already exists" substring: Pipeline.upsert, Experiment._load_or_create, _Trial._load_or_create, _TrialComponent._load_or_create, and common_utils._create_resource. All five stopped recognizing the collision and re-raised.

Changes:

  1. New shared predicate _is_resource_already_exists_error() in sagemaker-core/common_utils.py matching all known wordings (already exists, Cannot create already existing, must be unique within an AWS account) and codes (ValidationException, ResourceInUse). The uniqueness pattern is deliberately scoped with within an AWS account so definition-internal uniqueness errors (for example duplicate step names within a pipeline) are not mistaken for a name collision. All five call sites now use it.
  2. Pipeline.create(): on a name collision, log an ERROR pointing the caller at upsert() before re-raising. upsert() suppresses this hint via a keyword-only _log_name_collision_hint=False so the normal create-or-update path stays silent.
  3. ModelTrainer.train(): log actionable remediation before re-raising the original exception unchanged: ResourceLimitExceeded (parsed quota name, Service Quotas console link, explicit "retrying will keep failing"), AccessDenied (iam:PassRole hint), NoRegionError and NoCredentialsError (setup steps). No exception contract changes anywhere: every path re-raises the original exception.

Tests

  • sagemaker-core/tests/unit/test_common_utils.py: 9 new tests pinning the predicate against both old and new service wordings, ResourceInUse, non-collision ValidationExceptions, and the definition-internal uniqueness negative case (Step names must be unique within a pipeline must NOT match).
  • sagemaker-core/tests/unit/experiments/test_load_or_create.py (new): load-or-create for Experiment, _Trial, _TrialComponent parameterized over both wordings, plus re-raise of unrelated validation errors.
  • sagemaker-mlops/tests/unit/workflow/test_pipeline_class.py: upsert updates on the new wording; upsert of an existing pipeline emits no ERROR hint; bare create() collision logs the hint and re-raises.
  • sagemaker-train/tests/unit/train/test_model_trainer.py: ResourceLimitExceeded/NoRegionError/NoCredentialsError re-raise with guidance logged; AccessDenied hint; unrelated codes stay silent.

Manual verification

N/A. Unit coverage is sufficient: all changes are message matching and logging around unchanged exception flow, fully exercised by mocked ClientError responses copied verbatim from live service messages observed in SDK telemetry.

Screenshots

N/A, no user-visible UI change (client library).


…rrors

Telemetry shows the single largest V3 failure signature (40% of all
failures, 465 accounts over 90 days) is CreatePipeline/CreateExperiment
rejecting duplicate names. The service changed its error wording from
'... already exists' to '... names must be unique within an AWS
account ...', which silently broke every load-or-create flow that
matched the old substring: Pipeline.upsert, Experiment/_Trial/
_TrialComponent._load_or_create, and _create_resource re-raised
instead of loading or updating the existing resource.

- Add a shared _is_resource_already_exists_error predicate in
  common_utils that matches all known wordings and error codes, and
  use it at all five call sites.
- Pipeline.create: on a name collision, log guidance pointing at
  upsert() before re-raising.
- ModelTrainer.train: log actionable remediation for terminal
  CreateTrainingJob failures before re-raising unchanged --
  ResourceLimitExceeded (57% of ModelTrainer failures; quota name +
  Service Quotas link, flags that retries cannot succeed),
  AccessDenied (PassRole hint), NoRegionError and NoCredentialsError
  (setup steps).

All error paths re-raise the original exception; only logging is
added, so no caller contract changes.

---
X-AI-Prompt: implement error-pattern action items from PySDK telemetry deep dive
X-AI-Tool: Kiro
@jam-jee
jam-jee deployed to auto-approve September 10, 2026 21:20 — with GitHub Actions Active
@jam-jee
jam-jee deployed to auto-approve September 10, 2026 21:21 — with GitHub Actions Active
@jam-jee
jam-jee deployed to auto-approve September 10, 2026 21:21 — with GitHub Actions Active
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

Claude finished @jam-jee's task in 1m 33s —— View job


PR Review: fix(core): duplicate-name error wording + actionable train() errors

  • Read the full PR diff (/tmp/pr.diff)
  • Verify changed call sites and surrounding context
  • Assess correctness, backward-compat, tests, security
  • Post review

Overall this is a well-scoped, well-tested fix. The root-cause framing (service changed duplicate-name wording, five call sites matched the old substring) is accurate, the shared predicate consolidates the duplicated logic nicely, and every path preserves the original exception contract (re-raises unchanged). Test coverage is thorough and pins both old and new wordings. No blocking issues found.

A few observations, none blocking:

1. _is_resource_already_exists_error broadens matching at the experiment/trial/pipeline call sites — intentional, worth confirming.
sagemaker-core/src/sagemaker/core/common_utils.py
Previously Experiment/_Trial/_TrialComponent._load_or_create and Pipeline.upsert required exactly ValidationException + "already exists". The new predicate also accepts the ResourceInUse code and the "Cannot create already existing" pattern for those sites (they previously only lived in _create_resource). For create-or-load semantics this broadening is reasonable, but it does mean e.g. a ResourceInUse that isn't a name collision would now be swallowed into the load/update branch. Given these are create-then-load flows, that's an acceptable and probably desirable unification — just calling it out as a real behavior change beyond pure wording.

2. The within an AWS account scoping is a good defensive choice, but relies on stable service wording. The negative test (Step names must be unique within a pipeline) confirms definition-internal uniqueness won't be misread. Substring matching against service messages is inherently fragile (that's the whole reason for this PR), so this remains a maintenance risk — but it's well-documented in the module comment and pinned by tests, which is the right mitigation.

3. NoRegionError / NoCredentialsError are only caught around TrainingJob.create. sagemaker-train/src/sagemaker/train/model_trainer.py — region/credential resolution can also happen earlier (e.g. inside _create_training_job_args or when the boto session is first built), in which case the guidance wouldn't fire. This is a best-effort improvement that covers the common path, so it's fine; just noting it's not exhaustive.

4. Minor: _log_actionable_client_error quota regex. The r"service limit '([^']+)'" pattern matches the telemetry-sourced message format and is covered by a test with a verbatim message. If the service ever emits a differently-quoted quota name the hint simply degrades to the generic guidance (no crash), which is the right failure mode.

Security / correctness: No credential handling concerns — the new logging only echoes error codes/messages already returned by the service and static remediation text (no secrets). The create() keyword-only _log_name_collision_hint param is backward-compatible (positional callers unaffected). Logged messages contain no user-controlled injection risk beyond the service-provided quota name.

Tests: Coverage is strong — predicate pinned against both wordings + ResourceInUse + the internal-uniqueness negative case; load-or-create parameterized across resources; upsert vs. bare-create hint behavior; and all four train() error branches. Good.

Note: the inline-comment tool wasn't available in this environment, so this feedback is consolidated here rather than posted as line comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant