Skip to content

Fix retry handling, HttpClient ownership, and Bedrock credential edge cases#194

Open
InboraStudio wants to merge 1 commit into
anthropics:mainfrom
InboraStudio:main
Open

Fix retry handling, HttpClient ownership, and Bedrock credential edge cases#194
InboraStudio wants to merge 1 commit into
anthropics:mainfrom
InboraStudio:main

Conversation

@InboraStudio

Copy link
Copy Markdown

This PR fixes a few reliability and resource-management issues across the client, Bedrock adapter, and credentials layer.
No public API changes were made and all existing tests continue to pass.

What i have change

  • Fixed retry counter logic in Execute where retries could be counted twice in some failure paths.
  • Fixed 429 Too Many Requests not being retried on modern .NET runtimes due to an incorrect preprocessor condition.
  • Prevented accidental disposal of caller-owned HttpClient instances by tracking ownership internally.
  • Switched Bedrock content-type comparison to StringComparison.OrdinalIgnoreCase to avoid locale-specific issues.
  • Fixed AwsSigner.GetAuthorizationHeader mutating the caller’s headers dictionary during signing.
  • Removed an unnecessary .Replace("-", "") after Convert.ToHexStringLower.
  • Fixed unbounded growth of credential file lock entries in CredentialsFileProvider by pruning unused semaphores.
  • Added clearer comments around semaphore release paths in TokenCache to avoid future regressions.

fix(client): always retry 429 on all target frameworks

The previous #if !NETSTANDARD2_0_OR_GREATER guard silently excluded 429
from retry on .NET 8+ (and every other modern runtime) because the
NETSTANDARD2_0_OR_GREATER symbol is defined on all .NET 5+ targets, not
just netstandard. Remove the conditional and unconditionally retry 429.

fix(client): guard HttpClient disposal to only dispose SDK-owned clients

AnthropicClient and AnthropicClientWithRawResponse previously called
HttpClient.Dispose() unconditionally, which would destroy a caller-supplied
HttpClient (e.g., from IHttpClientFactory). Track OwnsHttpClient in
ClientOptions and skip disposal for externally-supplied clients.

fix(bedrock): use OrdinalIgnoreCase for Content-Type comparison

StringComparison.CurrentCultureIgnoreCase can fail in Turkish/Azerbaijani
locales where uppercase I maps to dotless i. HTTP MIME types are ASCII
tokens and must be compared with ordinal (byte-level) semantics.

fix(bedrock/signer): copy headers dict before mutating in AWSSigner

AWSSigner.GetAuthorizationHeader was adding host/x-amz-date/
x-amz-security-token directly to the caller-supplied headers dictionary.
If a caller reuses the dict across retries, these signing headers
accumulate, causing duplicate canonical headers and signature mismatches.
Now works on an internal copy.

fix(bedrock/signer): remove redundant .Replace after ToHexStringLower

Convert.ToHexStringLower never produces hyphens; the .Replace is dead code
that misleads readers into thinking hyphens are possible.

fix(credentials): add amortized file-lock pruning to CredentialsFileProvider

The static s_fileLocks ConcurrentDictionary<string, SemaphoreSlim> was
never pruned, causing a slow handle leak in long-running servers that rotate
credential file paths. PruneFileLocks() now removes idle semaphores on each
token-refresh cycle and the dictionary uses StringComparer.Ordinal.

fix(credentials): clarify semaphore-release contract in TokenCache

TryStartBackgroundRefresh held the semaphore across Task.Run scheduling.
Added comments documenting the three release paths and ensured the outer
catch releases on Task.Run failure (thread-pool exhaustion) with the same
comment as the code already implemented.
@InboraStudio InboraStudio requested a review from a team as a code owner May 13, 2026 07:32
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