Stop a case-differing AI connection name from silently replacing another - #185
Merged
Merged
Conversation
A connection's identifier hashes its lowercased client and connection name, so `Shared-Azure` under `CrestApps:AI:Connections` and `shared-azure` under `CrestApps:AI:Providers:Azure:Connections` resolve to the same identifier. The existing duplicate-name guard is suppressed when the identifiers match, and the final assignment overwrote the dictionary entry, so the later definition replaced the earlier one -- its endpoint and its credentials gone, with no diagnostic at any log level. Every deployment naming that connection then reached whichever resource happened to be read last, surfacing as an unexplained DeploymentNotFound when the model is deployed only on the other. The first definition now wins, matching how configured deployments are read, and the dropped entry is logged as a warning naming the entry it collided with. A failed Azure OpenAI completion also names the deployment, the model name that forms the request URL, the connection and the endpoint host it used. The provider's own exception names none of them, so a misrouted request read as a bare HTTP error. The streaming path enumerates by hand because a yield cannot sit inside a try block. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A connection's identifier hashes its lowercased client and connection name, so these two entries resolve to the same identifier:
AddConfiguredConnectionguards against a duplicate name only when the identifiers differ, and then ends with a plainconnections[connection.ItemId] = connection. Both conditions are met here, so the provider-section entry silently replaced the top-level one — its endpoint and its credentials gone, with no diagnostic at any log level.Every deployment naming that connection then reached whichever resource happened to be read last. That surfaces as an unexplained
HTTP 404 DeploymentNotFoundwhen the model is deployed only on the other resource, with nothing anywhere to suggest the connection the operator configured was not the one being used.This came out of a real staging incident: two connections named
WinnerWareandwinnerwarepointing at different Azure resources. One model worked, the other 404'd, and the config looked correct.What changed
ConfigurationAIProviderConnectionSource— first definition wins on an identifier collision, matchingConfigurationAIDeploymentSource.AddDeployment, and the dropped entry is logged as a warning naming the entry it collided with:AzureOpenAICompletionClient— a failed completion now logs the deployment name, the model name that forms the request URL, the connection name and the endpoint host. The provider's own exception names none of them. The streaming path enumerates by hand because ayieldcannot sit inside atry;OperationCanceledExceptionstill propagates unlogged.Changelog entries and a regression test.
Behavior change
A site that has such a pair today will switch to the earlier definition rather than the later one. That is the deterministic, source-order-consistent choice, and the new warning names both entries so the duplicate can be renamed. Noted in the changelog.
Testing
Full suite green — 3242 passed, 0 failed. The new test (
ConfigurationAIProviderConnectionStore_WhenTwoConfiguredNamesDifferOnlyByCase_ShouldKeepTheFirstOne) fails onmain: the surviving entry there is the second one, with the second endpoint.Not included
AIDeploymentManagerBase.ResolveSlotAsyncfalls through toGetFirstQualifiedDeploymentAsyncwhen the requested deployment cannot be resolved, substituting an arbitrary capable deployment with no log at all. Same class of silent surprise, but it wasn't the cause here — happy to do it separately.🤖 Generated with Claude Code