Skip to content

Expose canonical resolved maxAttempts via client.config.maxAttempts() #2069

Description

@arnulfojr

Describe the feature

client.config.maxAttempts() should return the canonical, resolved maximum attempt count the SDK actually uses for retries — i.e. the value from the configured retryStrategy when one is supplied, falling back to the client-level maxAttempts otherwise.

Use Case

A common observability need is to know, per request, whether an SDK client exhausted its retries — e.g. emitting a maxRetriesExceeded metric by comparing the attempts made ($metadata.attempts) against the configured attempt ceiling. This is a useful signal for alarming on clients that are repeatedly maxing out retries due to throttling or downstream failures.

Computing it requires the true attempt ceiling the SDK will honor. Today client.config.maxAttempts() only reflects the client-level setting. When a retryStrategy is passed, that value is ignored by the SDK in favor of the strategy's own count, so the public accessor returns a misleading value for those clients:

// Reflects 4
new S3Client({ maxAttempts: 4 });

// config.maxAttempts() still reports the client-level value,
// not the 2 the strategy will actually enforce
new S3Client({ retryStrategy: new StandardRetryStrategy(() => Promise.resolve(2)) });

The only current workaround is to branch on instanceof StandardRetryStrategy / AdaptiveRetryStrategy and read the strategy's maxAttempts() / maxAttemptsProvider() members. Those were private and broke in @smithy/util-retry v4.3.0 (#1922); v4.3.1 (#1950) re-exposed them, but they are still not declared on the shared RetryStrategyV2 interface, so any consumer relying on them stays coupled to concrete strategy classes and internal shape.

Proposed Solution

Resolve precedence inside the config layer so client.config.maxAttempts() always
returns the effective value:

  • if a retryStrategy is configured, return its resolved attempt count
  • otherwise return the client-level maxAttempts

This lets observability/telemetry code depend on a single stable public API and drop
all instanceof + private-member access. Declaring a documented maxAttempts()
member on RetryStrategyV2 would be an acceptable alternative.

Alternatives Considered

  • Reading strategy internals — fragile, broke once already (chore(util-retry): specification updates for retry 2.1 #1922), and not part of any public contract.

  • Inferring the ceiling from observed $metadata.attempts — impossible, since you can't distinguish "succeeded early" from "hit the cap" without knowing the cap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis issue is a bug.needs-triageThis issue or PR still needs to be triaged.response-requestedWaiting on additional info and feedback. Will move to 'closing-soon' in 5 days.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions