{"record":{"id":"d5c9f83270f224ba","repo":"microsoft/garnet","slug":"cannot-use-azurestorage-device-with-both-storage-s","errorCode":null,"errorMessage":"Cannot use AzureStorage device with both storage-string and storage-service-uri","messagePattern":"Cannot use AzureStorage device with both storage-string and storage-service-uri","errorType":"validation","errorClass":"InvalidAzureConfiguration","httpStatus":null,"severity":"critical","filePath":"libs/host/Configuration/Options.cs","lineNumber":757,"sourceCode":"\n        public GarnetServerOptions GetServerOptions(ILogger logger = null)\n        {\n            var enableStorageTier = EnableStorageTier.GetValueOrDefault();\n            var enableRevivification = EnableRevivification.GetValueOrDefault();\n\n            if (UseNativeDeviceLinux.GetValueOrDefault())\n            {\n                logger?.LogWarning(\"The --use-native-device-linux option is deprecated. Please use --device-type Native instead.\");\n                DeviceType = DeviceType.Native;\n            }\n\n            var deviceType = GetDeviceType(logger);\n\n            var useAzureStorage = deviceType == DeviceType.AzureStorage;\n            if (useAzureStorage && string.IsNullOrEmpty(AzureStorageConnectionString) && string.IsNullOrEmpty(AzureStorageServiceUri))\n                throw new InvalidAzureConfiguration(\"Cannot use AzureStorage device without supplying storage-string or storage-service-uri\");\n            if (useAzureStorage && !string.IsNullOrEmpty(AzureStorageConnectionString) && !string.IsNullOrEmpty(AzureStorageServiceUri))\n                throw new InvalidAzureConfiguration(\"Cannot use AzureStorage device with both storage-string and storage-service-uri\");\n\n            var logDir = LogDir;\n            if (!useAzureStorage && enableStorageTier) logDir = new DirectoryInfo(string.IsNullOrEmpty(logDir) ? \".\" : logDir).FullName;\n            var checkpointDir = CheckpointDir;\n            if (!useAzureStorage) checkpointDir = new DirectoryInfo(string.IsNullOrEmpty(checkpointDir) ? (string.IsNullOrEmpty(logDir) ? \".\" : logDir) : checkpointDir).FullName;\n\n            if (!Format.TryParseAddressList(Address, Port, out var endpoints, out _, ProtectedMode == CommandLineBooleanOption.True)\n              || endpoints.Length == 0)\n                throw new GarnetException($\"Invalid endpoint format {Address} {Port}.\");\n\n            EndPoint[] clusterAnnounceEndpoint = null;\n            if (ClusterAnnounceIp != null)\n            {\n                ClusterAnnouncePort = ClusterAnnouncePort == 0 ? Port : ClusterAnnouncePort;\n                clusterAnnounceEndpoint = Format.TryCreateEndpoint(ClusterAnnounceIp, ClusterAnnouncePort, tryConnect: false, logger: logger);\n                if (clusterAnnounceEndpoint == null || !endpoints.Any(endpoint =>\n                    endpoint is IPEndPoint listenEp && clusterAnnounceEndpoint[0] is IPEndPoint announceEp &&\n                    listenEp.Port == announceEp.Port &&","sourceCodeStart":739,"sourceCodeEnd":775,"githubUrl":"https://github.com/microsoft/garnet/blob/951b0fc6838721f89d102c2bbe1b914e8d39d700/libs/host/Configuration/Options.cs#L739-L775","documentation":"InvalidAzureConfiguration thrown during Options.Initialize when the device type is AzureStorage and BOTH AzureStorageConnectionString and AzureStorageServiceUri are non-empty. These two credential mechanisms are mutually exclusive: connection string uses a shared key/account-key, while service URI uses managed identity / token credentials. Supplying both creates ambiguity, so the guard rejects it at startup.","triggerScenarios":"Setting device-type to AzureStorage and providing both --storage-string and --storage-service-uri simultaneously in the command line, config file, or environment. This commonly happens when migrating from connection-string auth to managed identity without removing the old setting.","commonSituations":"Migration from connection-string to managed identity where the old storage-string was left in the config; a deployment template that sets both 'just in case'; merging two config sources (base + overlay) each setting a different credential.","solutions":["Remove one of the two: keep AzureStorageConnectionString for shared-key auth, or keep AzureStorageServiceUri for managed identity — not both.","If migrating to managed identity, delete the storage-string entry from config and environment.","Audit config overlays/templates to ensure only one credential source is set."],"exampleFix":"// before\noptions.AzureStorageConnectionString = \"DefaultEndpointsProtocol=...\";\noptions.AzureStorageServiceUri = new Uri(\"https://mystorage.blob.core.windows.net\");\n\n// after: keep only one\noptions.AzureStorageServiceUri = new Uri(\"https://mystorage.blob.core.windows.net\");\n// connection string removed","handlingStrategy":"validation","validationCode":"void ValidateAzureCredentialExclusivity(Options opts)\n{\n    if (!string.IsNullOrEmpty(opts.AzureStorageConnectionString) && !string.IsNullOrEmpty(opts.AzureStorageServiceUri))\n        throw new InvalidOperationException(\"Provide either storage-string OR storage-service-uri, not both.\");\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    options.Initialize(logger);\n}\ncatch (InvalidAzureConfiguration ex) when (ex.Message.Contains(\"both storage-string\"))\n{\n    logger.LogCritical(\"Remove one Azure credential source. Only one of storage-string or service-uri is allowed.\");\n    throw;\n}","preventionTips":["During migration from connection-string to managed identity, remove the old credential in the same change.","Use a config linter that flags mutually-exclusive Azure credential fields.","Keep base and overlay configs consistent in which credential source they define."],"tags":["config","azure-storage","startup","credentials","options","mutual-exclusion","validation"],"backgroundTag":null,"analyzedSha":"951b0fc6838721f89d102c2bbe1b914e8d39d700","analyzedAt":"2026-08-13T19:01:32.939Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}